();
private int restIndex = 0;
boolean shouldHideNotification = false;
private int MAX_SPEECH_SIZE = 4000;
private String getPlayerStateString(int state){
if (state == Player.STATE_READY) return "Ready";
if (state == Player.STATE_ENDED) return "Ended";
if (state == Player.STATE_IDLE) return "Idle";
if (state == Player.STATE_BUFFERING) return "Buffering";
return "Unknown";
}
@Override
public void onCreate() {
super.onCreate();
CustomPlayer player = new CustomPlayer(getMainLooper());
player.prepare();
mediaSession = new MediaSession.Builder(this, player)
.setId("sioyek")
.setCallback(new MediaSession.Callback() {
@Override
public int onPlayerCommandRequest(MediaSession session, MediaSession.ControllerInfo controller, int playerCommand) {
boolean isFromSioyek = controller.getPackageName().equals(getApplicationContext().getPackageName());
if (playerCommand == SimpleBasePlayer.COMMAND_PLAY_PAUSE){
if (tts.isSpeaking()){
pauseLocation = tempPauseLocation + pauseLocation;
tts.stop();
player.updatePlaybackState(Player.STATE_ENDED, true);
publishExternalTtsState(getPlayerStateString(Player.STATE_ENDED));
}
else{
String currentText = spokenText;
if (restIndex > 0){
currentText = restOfDocument.get(restIndex - 1);
}
tts.speak(currentText.substring(pauseLocation), TextToSpeech.QUEUE_FLUSH, null, "hi");
player.updatePlaybackState(Player.STATE_READY, true);
publishExternalTtsState(getPlayerStateString(Player.STATE_READY));
}
}
return MediaSession.Callback.super.onPlayerCommandRequest(session, controller, playerCommand);
}
})
.build();
tts = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
MAX_SPEECH_SIZE = tts.getMaxSpeechInputLength();
ttsInitialized = true;
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onStart(String s) {
publishTtsState("Speaking");
}
@Override
public void onDone(String s) {
publishTtsState("Ready");
if (restIndex < restOfDocument.size()){
tts.speak(restOfDocument.get(restIndex), TextToSpeech.QUEUE_FLUSH, null, "hi");
pauseLocation = 0;
tempPauseLocation = 0;
restIndex++;
}
// if (restOfDocument.length() > 0){
// tts.speak(restOfDocument, TextToSpeech.QUEUE_FLUSH, null, "hi");
// restOfDocument = "";
// }
}
@Override
public void onRangeStart(String utteranceId, int start, int end, int frame){
tempPauseLocation = start;
sendMessage(pauseLocation + start, pauseLocation + end);
}
@Override
public void onError(String s) {
publishTtsState("Error");
}
});
}
}
});
}
@Override
public int onStartCommand (Intent intent, int flags, int startId) {
if (intent.getStringExtra("text") != null){
spokenText = intent.getStringExtra("text");
pauseLocation = 0;
tempPauseLocation = 0;
}
if (intent.hasExtra("rate")){
float rate = intent.getFloatExtra("rate", 1.0f);
tts.setSpeechRate(rate);
}
if (intent.hasExtra("resume")){
handleResumeState();
}
if (intent.hasExtra("stop")){
}
if (intent.hasExtra("rest")){
String rest = intent.getStringExtra("rest");
setRestText(rest);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
Player player = mediaSession.getPlayer();
if (!player.getPlayWhenReady() || player.getMediaItemCount() == 0) {
stopForeground(true);
stopSelf();
}
}
@Override
public void onDestroy() {
mediaSession.release();
mediaSession = null;
super.onDestroy();
}
@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo){
SessionCommands commands = new SessionCommands.Builder().add(new SessionCommand("set_text", new Bundle())).build();
Player.Commands playerCommands = new Player.Commands.Builder().addAll(
Player.COMMAND_PLAY_PAUSE,
Player.COMMAND_GET_CURRENT_MEDIA_ITEM,
Player.COMMAND_GET_MEDIA_ITEMS_METADATA).build();
mediaSession.setAvailableCommands(controllerInfo, commands, playerCommands);
return mediaSession;
}
private void sendMessage(int begin, int end){
Intent intent = new Intent("sioyek_tts");
intent.putExtra("begin", begin);
intent.putExtra("end", end);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void publishTtsState(String state){
Intent intent = new Intent("sioyek_tts_state");
intent.putExtra("state", state);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
private void publishExternalTtsState(String state){
Intent intent = new Intent("sioyek_external_tts_state");
intent.putExtra("state", state);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
int findFirstSpaceBefore(String text, int index){
for (int i = index; i > 0; i--){
if (text.charAt(i) == ' '){
return i;
}
}
return 0;
}
void setRestText(String rest){
while (rest.length() > MAX_SPEECH_SIZE){
int index = findFirstSpaceBefore(rest, MAX_SPEECH_SIZE);
restOfDocument.add(rest.substring(0, index));
rest = rest.substring(index);
}
if (rest.length() > 0){
restOfDocument.add(rest);
}
restIndex = 0;
}
void handleResumeState(){
boolean isOnRest = restIndex > 0;
boolean isPlaying = tts.isSpeaking();
int offset = tempPauseLocation + pauseLocation;
if (isOnRest){
for (int i = 0; i < restIndex-1; i++){
offset += restOfDocument.get(i).length();
}
}
tts.stop();
pauseLocation = 0;
tempPauseLocation = 0;
restOfDocument.clear();
restIndex = 0;
SioyekActivity.onResumeState(isPlaying, isOnRest, offset);
}
}
================================================
FILE: build_and_release.sh
================================================
#!/usr/bin/env bash
set -e
if [ -z ${MAKE_PARALLEL+x} ]; then export MAKE_PARALLEL=$(nproc); else echo "MAKE_PARALLEL defined"; fi
echo "MAKE_PARALLEL set to $MAKE_PARALLEL"
# download linuxdeployqt if not exists
if [[ ! -f linuxdeployqt-continuous-x86_64.AppImage ]]; then
wget -q https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
chmod +x linuxdeployqt-continuous-x86_64.AppImage
fi
cd mupdf
make USE_SYSTEM_HARFBUZZ=yes -j$MAKE_PARALLEL
cd ..
if [ -z ${QMAKE+x} ]; then
QMAKE=qmake
fi
if [[ $1 == console ]]; then
$QMAKE "CONFIG+=linux_app_image console" pdf_viewer_build_config.pro
else
$QMAKE "CONFIG+=linux_app_image" pdf_viewer_build_config.pro
fi
rm -rf sioyek-release 2> /dev/null
make install INSTALL_ROOT=sioyek-release -j$MAKE_PARALLEL
cp pdf_viewer/prefs.config sioyek-release/usr/bin/prefs.config
cp pdf_viewer/prefs_user.config sioyek-release/usr/share/prefs_user.config
cp pdf_viewer/keys.config sioyek-release/usr/bin/keys.config
cp pdf_viewer/keys_user.config sioyek-release/usr/share/keys_user.config
cp -r pdf_viewer/shaders sioyek-release/usr/bin/shaders
cp tutorial.pdf sioyek-release/usr/bin/tutorial.pdf
./linuxdeployqt-continuous-x86_64.AppImage ./sioyek-release/usr/share/applications/sioyek.desktop -qmldir=./pdf_viewer/touchui -appimage
# ./linuxdeployqt-continuous-x86_64.AppImage ./sioyek-release/usr/share/applications/sioyek.desktop -appimage
mv Sioyek-* Sioyek-x86_64.AppImage
zip sioyek-release-linux.zip Sioyek-x86_64.AppImage
================================================
FILE: build_linux.sh
================================================
#!/usr/bin/env bash
set -e
# Compile mupdf
cd mupdf
make USE_SYSTEM_HARFBUZZ=yes -j$(nproc)
cd ..
# set QMAKE if not already defined
if [ -z "$QMAKE" ];
then
if [ -f "/usr/bin/qmake-qt6" ];
then
QMAKE="/usr/bin/qmake-qt6"
elif [ -f "/usr/bin/qmake" ];
then
QMAKE="/usr/bin/qmake"
else
QMAKE="qmake"
fi
fi
$QMAKE "CONFIG+=linux_app_image" pdf_viewer_build_config.pro
make -j$(nproc)
# Copy files in build/ subdirectory
rm -rf build 2> /dev/null
mkdir build
mv sioyek build/sioyek
cp pdf_viewer/prefs.config build/prefs.config
cp pdf_viewer/prefs_user.config build/prefs_user.config
cp pdf_viewer/keys.config build/keys.config
cp pdf_viewer/keys_user.config build/keys_user.config
cp -r pdf_viewer/shaders build/shaders
cp tutorial.pdf build/tutorial.pdf
================================================
FILE: build_mac.sh
================================================
#!/usr/bin/env bash
set -e
# prerequisite: brew install qt@5 freeglut mesa harfbuzz
#sys_glut_clfags=`pkg-config --cflags glut gl`
#sys_glut_libs=`pkg-config --libs glut gl`
#sys_harfbuzz_clfags=`pkg-config --cflags harfbuzz`
#sys_harfbuzz_libs=`pkg-config --libs harfbuzz`
if [ -z ${MAKE_PARALLEL+x} ]; then export MAKE_PARALLEL=1; else echo "MAKE_PARALLEL defined"; fi
echo "MAKE_PARALLEL set to $MAKE_PARALLEL"
cd mupdf
#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
make HAVE_GLUT=no -j$MAKE_PARALLEL
cd ..
sed -Ei '' "s/QMAKE_MACOSX_DEPLOYMENT_TARGET.=.[0-9]+/QMAKE_MACOSX_DEPLOYMENT_TARGET = $(sw_vers -productVersion | cut -d. -f1)/" pdf_viewer_build_config.pro
if [[ $1 == portable ]]; then
qmake pdf_viewer_build_config.pro
else
qmake "CONFIG+=non_portable" pdf_viewer_build_config.pro
fi
make -j$MAKE_PARALLEL
rm -rf build 2> /dev/null
mkdir build
mv sioyek.app build/
cp -r pdf_viewer/shaders build/sioyek.app/Contents/MacOS/shaders
cp pdf_viewer/prefs.config build/sioyek.app/Contents/MacOS/prefs.config
cp pdf_viewer/prefs_user.config build/sioyek.app/Contents/MacOS/prefs_user.config
cp pdf_viewer/keys.config build/sioyek.app/Contents/MacOS/keys.config
cp pdf_viewer/keys_user.config build/sioyek.app/Contents/MacOS/keys_user.config
cp tutorial.pdf build/sioyek.app/Contents/MacOS/tutorial.pdf
# Capture the current PATH
CURRENT_PATH=$(echo $PATH)
# Define the path to the Info.plist file inside the app bundle
INFO_PLIST="build/sioyek.app/Contents/Info.plist"
# Add LSEnvironment key with PATH to Info.plist
/usr/libexec/PlistBuddy -c "Add :LSEnvironment dict" "$INFO_PLIST" || echo "LSEnvironment already exists"
/usr/libexec/PlistBuddy -c "Add :LSEnvironment:PATH string $CURRENT_PATH" "$INFO_PLIST" || /usr/libexec/PlistBuddy -c "Set :LSEnvironment:PATH $CURRENT_PATH" "$INFO_PLIST"
# Hack is required to avoid race condition in macos in CI
# See https://github.com/actions/runner-images/issues/7522
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo killing...; sudo pkill -9 XProtect >/dev/null || true;
echo waiting...; while pgrep XProtect; do sleep 3; done;
fi
sleep 5
# mac deploys with qml currently don't work due to a qt bug
# macdeployqt build/sioyek.app -qmldir=./pdf_viewer/touchui -dmg
macdeployqt build/sioyek.app -dmg
codesign --force --deep --sign - build/sioyek.app
zip -r sioyek-release-mac.zip build/sioyek.dmg
================================================
FILE: build_windows.bat
================================================
cd mupdf\platform\win32\
msbuild -maxcpucount mupdf.sln /m /property:Configuration=Debug /property:MultiProcessorCompilation=true
msbuild -maxcpucount mupdf.sln /m /property:Configuration=Release /property:MultiProcessorCompilation=true
cd ..\..\..
cd zlib
nmake -f win32/makefile.msc
cd ..
qmake -tp vc "DEFINES+=NON_PORTABLE" "CONFIG+=release" pdf_viewer_build_config.pro
msbuild -maxcpucount sioyek.vcxproj /m /property:Configuration=Release
rm -r sioyek-release-windows 2> NUL
mkdir sioyek-release-windows
cp release\sioyek.exe sioyek-release-windows\sioyek.exe
cp pdf_viewer\keys.config sioyek-release-windows\keys.config
cp pdf_viewer\prefs.config sioyek-release-windows\prefs.config
cp -r pdf_viewer\shaders sioyek-release-windows\shaders
cp tutorial.pdf sioyek-release-windows\tutorial.pdf
windeployqt --qmldir ./pdf_viewer/touchui --release sioyek-release-windows\sioyek.exe
REM windeployqt sioyek-release-windows\sioyek.exe
cp windows_runtime\vcruntime140_1.dll sioyek-release-windows\vcruntime140_1.dll
cp windows_runtime\libssl-1_1-x64.dll sioyek-release-windows\libssl-1_1-x64.dll
cp windows_runtime\libcrypto-1_1-x64.dll sioyek-release-windows\libcrypto-1_1-x64.dll
if %1 == portable (
cp pdf_viewer\keys_user.config sioyek-release-windows\keys_user.config
cp pdf_viewer\prefs_user.config sioyek-release-windows\prefs_user.config
7z a sioyek-release-windows-portable.zip sioyek-release-windows
) else (
7z a sioyek-release-windows.zip sioyek-release-windows
)
================================================
FILE: data/command_docs.json
================================================
{"goto_begining": "\n
goto_begining
\n
Go to the beginning of the document. If prefixed with a number, it will go to that page number.
\n
\n", "goto_end": "\n
goto_end
\n
Go to the end of the document. If prefixed with a number, it will go to that page number.
\n
\n", "goto_definition": "\n
goto_definition
\n
While the visual ruler is highlighting a line, executing goto_definition will jump to the location of reference in that line. For example,\nexecuting this command while a line containing "Figure 2.1" is highlighted will jump to the location of the figure in the document.
\n
\n", "overview_definition": "\n
overview_definition
\n
Similar to goto_definition, but instead of jumping to the location of the reference, it will show a small overview of the reference in the\noverview window.
\n
\n", "portal_to_definition": "\n
portal_to_definition
\n
Similar to goto_definition, but instead of jumping to the location of the reference, it will create a portal from current location to the\nlocation of the definition.
\n
\n", "next_item": "\n
next_item
\n
While a search is in progress, this command will jump to the next search result.
\n
\n", "previous_item": "\n
previous_item
\n
While a search is in progress, this command will jump to the previous search result.
\n
\n", "set_mark": "\n
set_mark
\n
Mark the location with the symbol entered following the set_mark command.
\n
\n", "goto_mark": "\n
goto_mark
\n
Go to the mark previously set using set_mark.
\n
\n", "goto_page_with_page_number": "\n
goto_page_with_page_number
\n
Go to the page number corresponding to entered text.
\n
\n", "search": "\n
search
\n
Search for a text in document.
\n
\n", "regex_search": "\n
regex_search
\n
Search for a regular expression in the document.
\n
\n", "move_down": "\n
move_down, move_up, move_left, move_right
\n
Move the document.
\n
\n", "move_up": "\n
move_down, move_up, move_left, move_right
\n
Move the document.
\n
\n", "move_left": "\n
move_down, move_up, move_left, move_right
\n
Move the document.
\n
\n", "move_right": "\n
move_down, move_up, move_left, move_right
\n
Move the document.
\n
\n", "zoom_in": "\n
zoom_in and zoom_out
\n
Change the zoom level.
\n
\n", "zoom_out": "\n
zoom_in and zoom_out
\n
Change the zoom level.
\n
\n", "fit_to_page_width": "\n
fit_to_page_width and fit_to_page_height
\n
Fit the current page to window width/height.
\n
\n", "fit_to_page_height": "\n
fit_to_page_width and fit_to_page_height
\n
Fit the current page to window width/height.
\n
\n", "fit_to_page_width_smart": "\n
fit_to_page_width_smart and fit_to_page_height_smart
\n
Fit the current page (ignoring white margins) to window width/height.
\n
\n", "fit_to_page_height_smart": "\n
fit_to_page_width_smart and fit_to_page_height_smart
\n
Fit the current page (ignoring white margins) to window width/height.
\n
\n", "next_page": "\n
next_page and previous_page
\n
Navigate to the next/previous page.
\n
\n", "previous_page": "\n
next_page and previous_page
\n
Navigate to the next/previous page.
\n
\n", "open_document": "\n
open_document
\n
Open the file browser to open a document.
\n
\n", "add_bookmark": "\n
add_bookmark
\n
Add a bookmark to the current location at the document with the entered text.
\n
\n", "add_highlight": "\n
add_highlight
\n
Add the selected text as a highlight with the type of following symbol.
\n
\n", "goto_toc": "\n
goto_toc
\n
Open the table of contents.
\n
\n", "goto_bookmark": "\n
goto_bookmark
\n
Open bookmark list of current document.
\n
\n", "goto_highlight": "\n
goto_highlight
\n
Open highlight list of current document.
\n
\n", "goto_bookmark_g": "\n
goto_bookmark_g
\n
Open bookmark list of all documents.
\n
\n", "goto_highlight_g": "\n
goto_highlight_g
\n
Open highlight list of all documents.
\n
\n", "portal": "\n
portal
\n
Set the current location as the portal source. If a portal source is already set, then use the current\nlocation as the portal destination and create the portal.
\n
\n", "next_state": "\n
next_state, prev_state
\n
Go forward/backward in history.
\n
\n", "prev_state": "\n
next_state, prev_state
\n
Go forward/backward in history.
\n
\n", "delete_bookmark": "\n
delete_bookmark
\n
Delete the closest bookmark to the current location.
\n
\n", "delete_highlight": "\n
delete_highlight
\n
Delete the last highlight clicked on.
\n
\n", "delete_portal": "\n
delete_portal
\n
Delete the closest portal to the current location.
\n
\n", "goto_portal": "\n
goto_portal
\n
Go to the destination of the closest portal source to the current document location.
\n
\n", "edit_portal": "\n
edit_portal
\n
Jump to the destination of the closest portal. All the edits you do to the location/zoom is applied to the portal\nwhen you go back by executing prev_state.
\n
\n", "open_prev_doc": "\n
open_prev_doc
\n
Open a list of all opened document using sioyek.
\n
\n", "open_document_embedded": "\n
open_document_embedded
\n
Open an embedded file browser in sioyek.
\n
\n", "open_document_embedded_from_current_path": "\n
open_document_embedded_from_current_path
\n
Open an embedded file browser in sioyek. Initially, we are in the directory of current document.
\n
\n", "copy": "\n
copy
\n
Copy the selected text into clipboard.
\n
\n", "toggle_fullscreen": "\n
toggle_fullscreen
\n
Toggle fullscreen mode.
\n
\n", "toggle_one_window": "\n
toggle_one_window
\n
Open/close the helper window.
\n
\n", "toggle_highlight": "\n
toggle_highlight
\n
Toggle whether we highlight PDF links.
\n
\n", "toggle_synctex": "\n
toggle_synctex
\n
Toggle synctex mode. In synctex mode, right clicking on a PDF file opens the corresponding location in the latex file\nusing the configured text editor.
\n
\n", "command": "\n
command
\n
Open a list of all sioyek commands.
\n
\n", "external_search": "\n
external_search
\n
Search the selected text in the extenal search engine corresponding to the following symbol.
\n
\n", "open_selected_url": "\n
open_selected_url
\n
Open the selected URL in a browser.
\n
\n", "screen_up": "\n
screen_up and screen_down
\n
Move the screen up/down.
\n
\n", "screen_down": "\n
screen_up and screen_down
\n
Move the screen up/down.
\n
\n", "next_chapter": "\n
next_chapter and prev_chapter
\n
Go to the next/previous chapter in the current document.
\n
\n", "prev_chapter": "\n
next_chapter and prev_chapter
\n
Go to the next/previous chapter in the current document.
\n
\n", "toggle_dark_mode": "\n
toggle_dark_mode
\n
Switch between light/dark colorschemes.
\n
\n", "toggle_presentation_mode": "\n
toggle_presentation_mode
\n
Toggle presentation mode. In presentation mode we only show one page at a time and the current page\nfills the entire window.
\n
\n", "toggle_mouse_drag_mode": "\n
toggle_mouse_drag_mode
\n
Toggle mouse drag mode. In mouse drag mode, clicking and dragging using mouse moves the document (instead of selecting text).
\n
\n", "close_window": "\n
close_window
\n
Close the current window.
\n
\n", "quit": "\n
quit and q
\n
Quit sioyek (closes all windows).
\n
\n", "q": "\n
quit and q
\n
Quit sioyek (closes all windows).
\n
\n", "open_link": "\n
open_link
\n
Open the PDF links using keyboard. Each link will be shown with a label and the link corresponding to the entered label text will be opened.
\n
\n", "keyboard_select": "\n
keyboard_select
\n
Select text using keyboard. Each word in the document will be shown with a label. You should enter the labels corresponding to the\nbeginning and end of the selection (separated by a space).
\n
\n", "keyboard_smart_jump": "\n
keyboard_smart_jump and keyboard_overview
\n
Perform a smartjump (or open an overview) using keyboard. Each word in the document will be shown with a label and we will smart jump to the location of the\nreference corresponding to the entered label.
\n
\n", "keyboard_overview": "\n
keyboard_smart_jump and keyboard_overview
\n
Perform a smartjump (or open an overview) using keyboard. Each word in the document will be shown with a label and we will smart jump to the location of the\nreference corresponding to the entered label.
\n
\n", "keys": "\n
keys and prefs
\n
Open the keys.config / prefs.config file.
\n
\n", "prefs": "\n
keys and prefs
\n
Open the keys.config / prefs.config file.
\n
\n", "keys_user": "\n
keys_user and prefs_user
\n
Open the keys_user.config / prefs_user.config file.
\n
\n", "prefs_user": "\n
keys_user and prefs_user
\n
Open the keys_user.config / prefs_user.config file.
\n
\n", "export": "\n
export and import
\n
Export/import sioyek data to/from a json file.
\n
\n", "import": "\n
export and import
\n
Export/import sioyek data to/from a json file.
\n
\n", "enter_visual_mark_mode": "\n
enter_visual_mark_mode
\n
Place a visual mark at the center of screen.
\n
\n", "move_visual_mark_down": "\n
move_visual_mark_down and move_visual_mark_up
\n
Move the visual mark up and down which highlights the previous/next line.
\n
\n", "move_visual_mark_up": "\n
move_visual_mark_down and move_visual_mark_up
\n
Move the visual mark up and down which highlights the previous/next line.
\n
\n", "toggle_visual_scroll": "\n", "toggle_horizontal_scroll_lock": "\n", "toggle_custom_color": "\n
toggle_custom_color
\n
Toggle custom color mode. In this mode, we change the text/background color of the document to the colors specified in prefs_user.config.
\n
\n", "execute": "\n
execute
\n
Execute the entered text as a shell command.
\n
\n", "execute_predefined_command": "\n
execute_predefined_command
\n
Execute the predefined command (in prefs_user.config) corresponding to the following symbol.
\n
\n", "embed_annotations": "\n
embed_annotations
\n
Export a new version of the current document with all sioyek annotations embedded so that they are visible in other software.
\n
\n", "copy_window_size_config": "\n
copy_window_size_config
\n
Copy the current window size configuration (useful when you want to set the configs in prefs_user.config).
\n
\n", "toggle_select_highlight": "\n
toggle_select_highlight
\n
Toggle select highlight mode. In this mode, just selecting the text automatically highlights it.
\n
\n", "set_select_highlight_type": "\n
set_select_highlight_type
\n
Set the highlight type used in select highlight mode.
\n
\n", "open_last_document": "\n
open_last_document
\n
Open the previous document.
\n
\n", "toggle_window_configuration": "\n
toggle_window_configuration
\n
Toggle between one window/two window configuration settings.
\n
\n", "prefs_user_all": "\n
prefs_user_all and keys_user_all
\n
Show a list of all prefs_user.config / keys_user.config files discovered by sioyek.
\n
\n", "keys_user_all": "\n
prefs_user_all and keys_user_all
\n
Show a list of all prefs_user.config / keys_user.config files discovered by sioyek.
\n
\n", "fit_to_page_width_ratio": "\n
fit_to_page_width_ratio
\n
Similar to fit_to_page_width but instead of fitting to the entire screen width, we fit to a ratio configured in\nprefs_user.config.
\n
\n", "smart_jump_under_cursor": "\n
smart_jump_under_cursor and overview_under_cursor
\n
Perform a smart jump (or open an overview) to the reference under the mouse cursor.
\n
\n", "overview_under_cursor": "\n
smart_jump_under_cursor and overview_under_cursor
\n
Perform a smart jump (or open an overview) to the reference under the mouse cursor.
\n
\n", "close_overview": "\n
close_overview
\n
Close the overview window.
\n
\n", "visual_mark_under_cursor": "\n
visual_mark_under_cursor
\n
Place a visual mark (ruler) under the mouse cursor.
\n
\n", "close_visual_mark": "\n
close_visual_mark
\n
Exit the visual mark (ruler) mode.
\n
\n", "zoom_in_cursor": "\n
zoom_in_cursor and zoom_out_cursor
\n
Zoom in/out on the mouse cursor.
\n
\n", "zoom_out_cursor": "\n
zoom_in_cursor and zoom_out_cursor
\n
Zoom in/out on the mouse cursor.
\n
\n", "goto_left": "\n
goto_left, goto_right, goto_top_of_page and goto_bottom_of_page
\n
Go to the left/right/top/bottom of the current page.
\n
\n", "goto_right": "\n
goto_left, goto_right, goto_top_of_page and goto_bottom_of_page
\n
Go to the left/right/top/bottom of the current page.
\n
\n", "goto_top_of_page": "\n
goto_left, goto_right, goto_top_of_page and goto_bottom_of_page
\n
Go to the left/right/top/bottom of the current page.
\n
\n", "goto_bottom_of_page": "\n
goto_left, goto_right, goto_top_of_page and goto_bottom_of_page
\n
Go to the left/right/top/bottom of the current page.
\n
\n", "goto_left_smart": "\n
goto_left_smart and goto_right_smart
\n
Go to the left/right side of the current page ignoring white margins.
\n
\n", "goto_right_smart": "\n
goto_left_smart and goto_right_smart
\n
Go to the left/right side of the current page ignoring white margins.
\n
\n", "rotate_clockwise": "\n
rotate_clockwise and rotate_counterclockwise
\n
Rotate the document.
\n
\n", "rotate_counterclockwise": "\n
rotate_clockwise and rotate_counterclockwise
\n
Rotate the document.
\n
\n", "goto_next_highlight": "\n
goto_next_highlight and goto_prev_highlight
\n
Go to the next/previous highlight in the current document.
\n
\n", "goto_prev_highlight": "\n
goto_next_highlight and goto_prev_highlight
\n
Go to the next/previous highlight in the current document.
\n
\n", "goto_next_highlight_of_type": "\n
goto_next_highlight_of_type and goto_prev_highlight_of_type
\n
Go to the next/previous highlight in the current document with following symbol's type.
\n
\n", "goto_prev_highlight_of_type": "\n
goto_next_highlight_of_type and goto_prev_highlight_of_type
\n
Go to the next/previous highlight in the current document with following symbol's type.
\n
\n", "add_highlight_with_current_type": "\n
add_highlight_with_current_type
\n
Add a highlight with the type specified using set_select_highlight_type.
\n
\n", "enter_password": "\n
enter_password
\n
Enter the password for password-protected documents.
\n
\n", "toggle_fastread": "\n
toggle_fastread
\n
Highlight the first few characters of each word. Supposedly it may increase reading speed (unconfirmed).
\n
\n", "new_window": "\n
new_window
\n
Open a new sioyek window.
\n
\n", "toggle_statusbar": "\n
toggle_statusbar
\n
Toggle the statusbar at the bottom of the screen.
\n
\n", "reload": "\n
reload
\n
Reload the current document.
\n
\n", "synctex_under_cursor": "\n
synctex_under_cursor
\n
Perform a synctex search for the location under mouse cursor.
\n
\n", "set_status_string": "\n
set_status_string
\n
Set a message to be displayed in sioyek's statusbar.
\n
\n", "clear_status_string": "\n
clear_status_string
\n
Clear the message in sioyek's statusbar.
\n
\n", "toggle_titlebar": "\n
toggle_titlebar
\n
Toggle the window titlebar.
\n
\n", "next_preview": "\n
next_preview and previous_preview
\n
If there are multiple possible previews for the overview window, move to the next/previous preview.
\n
\n", "previous_preview": "\n
next_preview and previous_preview
\n
If there are multiple possible previews for the overview window, move to the next/previous preview.
\n
\n", "goto_overview": "\n
goto_overview and portal_to_overview
\n
Go to / create a portal to the location displayed in the overview window.
\n
\n", "portal_to_overview": "\n
goto_overview and portal_to_overview
\n
Go to / create a portal to the location displayed in the overview window.
\n
\n", "goto_selected_text": "\n
goto_selected_text
\n
Jump to the location of current selected text.
\n
\n", "focus_text": "\n
focus_text
\n
If in visual mark (ruler) mode, focus the ruler on the line corresponding to the entered text.
\n
\n", "goto_window": "\n
goto_window
\n
Open a searchable list of sioyek windows.
\n
\n", "toggle_smooth_scroll_mode": "\n", "toggle_scrollbar": "\n", "overview_to_portal": "\n
overview_to_portal
\n
Open the overview window to the closest portal to current document location.
\n
\n", "select_rect": "\n
select_rect
\n
Select a rectangle (can be used in extensions using %{selected_rectangle} variable).
\n
\n", "donate": "\n
donate
\n
Open donation page.
\n
\n", "overview_next_item": "\n
overview_next_item and overview_prev_item
\n\n", "overview_prev_item": "\n
overview_next_item and overview_prev_item
\n\n"}
================================================
FILE: data/config_docs.json
================================================
{"background_color": "\n
background_color
\n
Specifies the background color of the app (this is different from the background color of PDF page which is configured using :ref:`customcolor`, this color is only shown when the displayed page is smaller than the srceen). The syntax to set colors is:
\n
\n
System Message: ERROR/3 (<string>, line 3); backlink
\nUnknown interpreted text role "ref".
\n
\nbackground_color r g b\n
\n
where r,g and b red, green and blue values between 0 and 1. For example in order to set the background color to gray we can add the following to our prefs_user.config:
\n
\nbackground_color 0.5 0.5 0.5\n
\n
\n", "dark_mode_background_color": "\n
dark_mode_background_color
\n
Specifies the background color when dark mode is enabled.
\n
\n", "dark_mode_contrast": "\n
dark_mode_contrast
\n
White text in dark mode can be annoying for the eyes. This option allows us to dim the white colors when dark mode is enabled. Allowed values are between 0.0 and 1.0.
\n
\n", "text_highlight_color": "\n
text_highlight_color
\n
Highlight color when text is selected using mouse.
\n
\n", "visual_mark_color": "\n
visual_mark_color
\n
This is the color of the transparent visual mark explained in :ref:`usage:Visual Mark` (this feature originally had an entirely different functionality which is why it is called vertical line even though there is nothing vertical about it!).\nAllowed values are RGBA colors between 0.0 and 1.0. For example, to set the color to a transparent red we add the following to our prefs_user.config:
\n
\n
System Message: ERROR/3 (<string>, line 4); backlink
\nUnknown interpreted text role "ref".
\n
\nvisual_mark_color 1.0 0.0 0.0 0.1\n
\n
\n", "ruler_mode": "\n
ruler_mode
\n
If it is 1, we highlight a rectangle around the current line in visual mark mode. Otherwise, we highlight below the current line.
\n
\nruler_mode 1\n
\n
\n", "ruler_padding": "\n
ruler_padding and ruler_x_padding
\n
Additional padding for ruler. Makes the ruler a little larger and more readable.
\n
\nruler_padding 1.0\nruler_x_padding 5.0\n
\n
\n", "ruler_x_padding": "\n
ruler_padding and ruler_x_padding
\n
Additional padding for ruler. Makes the ruler a little larger and more readable.
\n
\nruler_padding 1.0\nruler_x_padding 5.0\n
\n
\n", "visual_mark_next_page_fraction": "\n
visual_mark_next_page_fraction
\n
When we go to the next page while in visual mark mode, this setting determines which location of screen the new line should be located at. The values are between -1 and 1. With 0 being the middle of the screen and 1 and -1 being the top and bottom of the screen respectively.
\n
\nvisual_mark_next_page_fraction 0.5\n
\n
\n", "visual_mark_next_page_threshold": "\n
visual_mark_next_page_threshold
\n
Determines at which point in screen we move to the next page. Acceptable range is between 0 and visual_mark_next_page_fraction.
\n
\n", "search_highlight_color": "\n
search_highlight_color
\n
The color used to highlight search results.
\n
\n", "link_highlight_color": "\n
link_highlight_color
\n
The color used to highlight links in PDF files.
\n
\n", "synctex_highlight_color": "\n
synctex_highlight_color
\n
Highlight color for synctex forward search highlights.
\n
\n", "search_url_*": "\n
search_url_a to search_url_z
\n
The web addresses used for performing external_search command. (see :ref:`usage:External Search`). Example:
\n
\n
System Message: ERROR/3 (<string>, line 4); backlink
\nUnknown interpreted text role "ref".
\n
\nsearch_url_g https://www.google.com/search?q=\n
\n
\n", "middle_click_search_engine": "\n
middle_click_search_engine and shift_middle_click_search_engine
\n
The letter corresponding to search_url_* configs to use when middle clicking/shift-middle clicking on text.\nExample:
\n
\nmiddle_click_search_engine g\n
\n
This causes the search engine configures using search_url_g to be used when middle clicking on text.
\n
\n", "shift_middle_click_search_engine": "\n
middle_click_search_engine and shift_middle_click_search_engine
\n
The letter corresponding to search_url_* configs to use when middle clicking/shift-middle clicking on text.\nExample:
\n
\nmiddle_click_search_engine g\n
\n
This causes the search engine configures using search_url_g to be used when middle clicking on text.
\n
\n", "zoom_inc_factor": "\n
zoom_inc_factor
\n
The fraction by which we enlarge the page when zooming in/out.
\n
\n", "wheel_zoom_on_cursor": "\n
wheel_zoom_on_cursor
\n
If set, when using mouse wheel to zoom we zoom in on mouse cursor instead of middle of screen.
\n
\n", "vertical_move_amount": "\n
vertical_move_amount and horizontal_move_amount
\n
How many inches we move vertically/horizontally when performing move_* commands.
\n
\n", "horizontal_move_amount": "\n
vertical_move_amount and horizontal_move_amount
\n
How many inches we move vertically/horizontally when performing move_* commands.
\n
\n", "move_screen_ratio": "\n
move_screen_ratio
\n
The fraction of screen by which we move when executing screen_down and screen_up commands. (note that despite the name, the values are fractions between 0 and 1, not percentages)
\n
\n", "flat_toc": "\n
flat_toc
\n
Displays a simplified flat table of contents instead of a hierarchial one. This can improve performance for documents with very large number of table of contents entries (thousands).\nAcceptable values are 0 and 1.
\n
\n", "collapsed_toc": "\n
collapsed_toc
\n
If set, we initially collapse all table of content entries.
\n
\n", "should_use_multiple_monitors": "\n
should_use_multiple_monitors
\n
If it is 1, when launching the application if we detect multiple monitors, we automatically launch the helper window in second monitor.\nAcceptable values are 0 and 1.
\n
\n", "should_load_tutorial_when_no_other_file": "\n
should_load_tutorial_when_no_other_file
\n
If the last opened document is empty, load the tutorial pdf instead.
\n
\n", "should_launch_new_instance": "\n
should_launch_new_instance
\n
\n
Warning
\n
This is deprecated. Use should_launch_new_window instead.
\n
\n
If it is 0, then we use the previous instance of sioyek when launching a new file, otherwise a new instance is launched every time we open a new file.
\n
\n", "should_launch_new_window": "\n
should_launch_new_window
\n
If it is 0, then we use the previous window of sioyek when opening a new file, otherwise a new window is opened every time we open a new file.
\n
\n", "inverse_search_command": "\n
inverse_search_command
\n
The command to use when trying to do inverse search into a LaTeX document. %1 expands to the name of the file and %2 expans to the line number. For example:
\n
\ninverse_search_command "C:\\path\\to\\vscode\\Code.exe" -r -g %1:%2\n
\n
\n", "highlight_color_*": "\n
highlight_color_a to highlight_color_z
\n
The color to use for highlights of type a to z.
\n
\n", "should_draw_unrendered_pages": "\n
should_draw_unrendered_pages
\n
If set, we display a checkerboard pattern for unrendered pages (by default we display nothing).
\n
\n", "rerender_overview": "\n
rerender_overview
\n
Normally we reuse the rendered page for overview window. This may cause the overview page to be blurry or too sharp if there is a significant difference between the zoom levels of the main window and overview window.\nIf rerender_overview is set, we rerender overview which solves this issue at the cost of some additional computation.
\n
\nrerender_overview 1\n
\n
\n", "default_dark_mode": "\n
default_dark_mode
\n
Use dark mode by default.
\n
\n", "sort_bookmarks_by_location": "\n
sort_bookmarks_by_location
\n
If set, we sort the bookmarks by their location instead of their creation time.
\n
\n", "shared_database_path": "\n
shared_database_path
\n
The path of shared.db database file. You can set this path to be in a synchronized folder (for example a dropbox folder) and sioyek data will be automatically synchronized across your devices.
\n
\n", "font_size": "\n
font_size
\n
Size of the UI font.
\n
\n", "ui_font": "\n
ui_font
\n
The font to use for UI text.
\n
\nui_font Segoe UI Emoji\n
\n
\n", "item_list_prefix": "\n
item_list_prefix
\n
A prefix character to use before list of items (for example can be used to display a checkmark before each of the bookmarks).
\n
\nitem_list_prefix \u2714\ufe0f\n
\n
\n", "check_for_updates_on_startup": "\n
check_for_updates_on_startup
\n
If set, sioyek checkes for new versions on startup and notifies the user if a new version if available.
\n
\ncheck_for_updates_on_startup 1\n
\n
\n", "custom_background_color": "\n
custom_background_color and custom_text_color
\n
Specify the background and text color when using custom color mode (by executing toggle_custom_color command).
\n
\ncustom_background_color 0.18 0.20 0.25\ncustom_text_color 1.0 1.0 1.0\n
\n
\n", "custom_text_color": "\n
custom_background_color and custom_text_color
\n
Specify the background and text color when using custom color mode (by executing toggle_custom_color command).
\n
\ncustom_background_color 0.18 0.20 0.25\ncustom_text_color 1.0 1.0 1.0\n
\n
\n", "startup_commands": "\n
startup_commands
\n
Semicolon-separated list of commands to execute on startup. For example, to start in custom color mode and in visual scroll mode, you can add the following (the command names are the same as the ones displayed when opening the command window using :):
\n
\nstartup_commands toggle_custom_color;toggle_visual_scroll\n
\n
\n", "status_bar_color": "\n
status_bar_color, status_bar_text_color and status_bar_font_size
\n
Allow you to customize the appearance of status bar.
\n
\nstatus_bar_color 0 0 0\nstatus_bar_text_color 1 1 1\nstatus_bar_font_size 10\n
\n
\n", "status_bar_text_color": "\n
status_bar_color, status_bar_text_color and status_bar_font_size
\n
Allow you to customize the appearance of status bar.
\n
\nstatus_bar_color 0 0 0\nstatus_bar_text_color 1 1 1\nstatus_bar_font_size 10\n
\n
\n", "status_bar_font_size": "\n
status_bar_color, status_bar_text_color and status_bar_font_size
\n
Allow you to customize the appearance of status bar.
\n
\nstatus_bar_color 0 0 0\nstatus_bar_text_color 1 1 1\nstatus_bar_font_size 10\n
\n
\n", "execute_command_*": "\n
execute_command_a to execute_command_z
\n
Predefined shell commands to be executed using execute_predefined_command. %1 expands to the path of the current file, %2 expands to name of the current file and %3 expands to current selected text.\nFor example, suppose you have a command named ocr which takes a file path and produces an OCR'd version of the document. You can add the following to you prefs_user.config:
\n
\nexecute_command_o ocr "%1"\n
\n
You can later quickly invoke this command by executing execute_predefined_command and then pressing o.
\n
\n
Warning
\n
The command parsing code in sioyek is not very good. For example it can not handle multiple commands like command1 args;command2 or commands that include spaces. If you want to run a complex command, first put all commands in a script file and then run the script file using using sioyek like this: /path/to/script.sh %1 %2 %3.
\n
\n
\n", "papers_folder_path": "\n
papers_folder_path
\n
Path to a directory on your computer. Sioyek monitors the changes in this directory and if a new file is added to this directory while we have a pending portal, this file is automatically used as the destination of the portal. This is useful when creating a portal from a reference in a paper to the actual reference file.
\n
\n", "display_resolution_scale": "\n
display_resolution_scale
\n
Manual resolution scaling. Can be useful for some very high resolution displays which report the wrong resolution.
\n
\n", "linear_filter": "\n
linear_filter
\n
If set, we use linear texture filtering instead of the normal nearest neighbour filtering. This is useful when using manual display resolution scale which causes the nearest neighbour filter to look bad.
\n
\n", "main_window_size": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "main_window_move": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "helper_window_size": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "helper_window_move": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "single_main_window_size": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "single_main_window_move": "\n
main_window_size, main_window_move, helper_window_size, helper_window_move, single_main_window_size and single_main_window_move
\n
Configures the size and position of the main window and the helper window. single_main_window_* is used when helper window is closed and the other configs are used when both windows are opened.\nThese values are automatically written to auto.cong file when sioyek exits but you can manually override them by setting them in your prefs_user.config.
\n
\nsingle_main_window_size 1824 988\nsingle_main_window_move 22 21\nmain_window_size 1824 988\nmain_window_move 18 44\nhelper_window_size 1891 1033\nhelper_window_move 1951 0\n
\n
\n", "touchpad_sensitivity": "\n
touchpad_sensitivity
\n
Can be used to adjust the sensitivity of the touchpad.
\n
\ntouchpad_sensitivity 0.1\n
\n
\n", "page_separator_width": "\n
page_separator_width and page_separator_color
\n
Used to adjust the appearance of page separator.
\n
\npage_separator_width 2\npage_separator_color 0.5 0.5 0.5\n
\n
\n", "page_separator_color": "\n
page_separator_width and page_separator_color
\n
Used to adjust the appearance of page separator.
\n
\npage_separator_width 2\npage_separator_color 0.5 0.5 0.5\n
\n
\n", "fit_to_page_width_ratio": "\n
fit_to_page_width_ratio
\n
Ratio of screen width to use when using fit_to_screen_width_ratio command. Can be useful for very wide screens.
\n
\nfit_to_page_width_ratio 0.75\n
\n
\n", "create_table_of_contents_if_not_exists": "\n
create_table_of_contents_if_not_exists
\n
If set and the file doesn't have a table of contents, we use heuristic methods to create a table of contents. You can use max_created_toc_size to prevent creating very large table of contents.
\n
\ncreate_table_of_contents_if_not_exists 1\nmax_created_toc_size 5000\n
\n
\n", "force_custom_line_algorithm": "\n
force_custom_line_algorithm
\n
Use legacy line detection algorithm instead of the mupdf one.
\n
\n", "overview_size": "\n
overview_size and overview_offset
\n
Adjust the size of overview window. The values are in normalized window coordinates between -1 and 1.
\n
\noverview_size 0.852604 0.597729\noverview_offset -0.0119792 0.120151\n
\n
\n", "overview_offset": "\n
overview_size and overview_offset
\n
Adjust the size of overview window. The values are in normalized window coordinates between -1 and 1.
\n
\noverview_size 0.852604 0.597729\noverview_offset -0.0119792 0.120151\n
\n
\n", "show_doc_path": "\n
show_doc_path
\n
Show the full document path instead of just the file name in list of recently opened documents.
\n
\n", "should_warn_about_user_key_override": "\n
should_warn_about_user_key_override
\n
If set, we warn the user in command line when overriding already degined keybinds.
\n
\n", "single_click_selects_words": "\n
single_click_selects_words
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "shift_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "control_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "alt_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "shift_right_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "control_right_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "alt_right_click_command": "\n
shift_click_command, control_click_command, alt_click_command, shift_right_click_command, control_right_click_command, and alt_right_click_command
\n
Custom commands to run when mouse click is pressed while modifier keys are held down. For example:
\n
\ncontrol_click_command overview_under_cursor\n
\n
If set, single clicking and dragging mouse selects entire words rather than characters.
\n
\n", "use_legacy_keybinds": "\n
use_legacy_keybinds
\n
By default we use legacy keybindings which have some problems. For example to bind the % key, you would have to enter something like this:
\n
\ncommand <S-%>\n
\n
which is a little weird. Also legacy keybinds don't work well with some keyboard layouts. If you set use_legacy_keybinds to 0, then we use a new method for keybind parsing which allows you to do something like this:
\n
\ncommand %\n
\n
which also works with all keyboard layouts. Since this is a backwards incompatible change, use_legacy_keybinds is activated by default.
\n
\n", "multiline_menus": "\n", "start_with_helper_window": "\n
start_with_helper_window
\n
Open helper window when sioyek starts.
\n
\n", "prerender_next_page_presentation": "\n
prerender_next_page_presentation
\n
When in presentation mode, we pre-render the next page to remove flickering when moving between pages.
\n
\n", "highlight_middle_click": "\n
highlight_middle_click
\n
If enabled, highlights the selected text by middle clicking on it.
\n
\n", "smooth_scroll_speed": "\n", "smooth_scroll_drag": "\n", "super_fast_search": "\n
super_fast_search
\n
If enabled, indexes the document text and considerably speeds of searching. It also enables regex_search command which uses the index to search for regular expressions.
\n
\n", "show_closest_bookmark_in_statusbar": "\n
show_closest_bookmark_in_statusbar
\n
If enabled, displays the text for closest bookmark in the statusbar.
\n
\n", "show_close_portal_in_statusbar": "\n
show_close_portal_in_statusbar
\n
If enabled, it displays a statusbar message when we are close to a portal.
\n
\n", "prerender_page_count": "\n
prerender_page_count
\n
The number of pages to prerender. The larger it is, the more memory we use but there will be less flickering when quickly moving pages.
\n
\n", "case_sensitive_search": "\n
case_sensitive_search
\n
It is enabled by default. If disabled, we use case-insensitive searching.
\n
\n", "show_document_name_in_statusbar": "\n
show_document_name_in_statusbar
\n
Displays the document name in sioyek's statusbar.
\n
\n", "ui_background_color": "\n
ui_background_color and ui_text_color
\n
The background and text color of (unselected) UI elements.
\n
\n", "ui_text_color": "\n
ui_background_color and ui_text_color
\n
The background and text color of (unselected) UI elements.
\n
\n", "ui_selected_background_color": "\n
ui_selected_background_color and ui_selected_text_color
\n
The background and text color of selected UI elements.
\n
\n", "ui_selected_text_color": "\n
ui_selected_background_color and ui_selected_text_color
\n
The background and text color of selected UI elements.
\n
\n", "numeric_tags": "\n", "source": "\n
source
\n
Includes another config file, which is useful for themes and extensions.
\n
\n# includes file.config in this configuration file\n
\n
\n"}
================================================
FILE: delete_build.sh
================================================
rm -r Sioyek*
rm sioyek
rm *.o
rm -r sioyek-release
================================================
FILE: deploy.py
================================================
import os
if __name__ == '__main__':
with open('E:\\labs\hexomancer-sioyek\\sioyek\\last_version.txt', 'r') as infile:
last_version = int(infile.read())
new_version = last_version + 1
with open('E:\\labs\hexomancer-sioyek\\sioyek\\last_version.txt', 'w') as outfile:
outfile.write(str(new_version))
os.chdir('E:\\labs\hexomancer-sioyek\\sioyek')
os.system("git pull upstream main")
os.system(f"git tag v0.31.{new_version}")
os.system("git push origin main")
os.system("git push origin --tags")
# os.system(f"git push origin v0.31.{new_version}")
os.system("pause")
================================================
FILE: fzf/fzf.c
================================================
#include "fzf.h"
#include
#include
#include
// TODO(conni2461): UNICODE HEADER
#define UNICODE_MAXASCII 0x7f
#define SFREE(x) \
if (x) { \
free(x); \
}
/* Helpers */
#define free_alloc(obj) \
if ((obj).allocated) { \
free((obj).data); \
}
#define gen_simple_slice(name, type) \
typedef struct { \
type *data; \
size_t size; \
} name##_slice_t; \
static name##_slice_t slice_##name(type *input, size_t from, size_t to) { \
return (name##_slice_t){.data = input + from, .size = to - from}; \
}
#define gen_slice(name, type) \
gen_simple_slice(name, type); \
static name##_slice_t slice_##name##_right(type *input, size_t to) { \
return slice_##name(input, 0, to); \
}
gen_slice(i16, int16_t);
gen_simple_slice(i32, int32_t);
gen_slice(str, const char);
#undef gen_slice
#undef gen_simple_slice
/* TODO(conni2461): additional types (utf8) */
typedef int32_t char_class;
typedef char byte;
typedef enum {
ScoreMatch = 16,
ScoreGapStart = -3,
ScoreGapExtention = -1,
BonusBoundary = ScoreMatch / 2,
BonusNonWord = ScoreMatch / 2,
BonusCamel123 = BonusBoundary + ScoreGapExtention,
BonusConsecutive = -(ScoreGapStart + ScoreGapExtention),
BonusFirstCharMultiplier = 2,
} score_t;
typedef enum {
CharNonWord = 0,
CharLower,
CharUpper,
CharLetter,
CharNumber
} char_types;
static int32_t index_byte(fzf_string_t *string, char b) {
for (size_t i = 0; i < string->size; i++) {
if (string->data[i] == b) {
return (int32_t)i;
}
}
return -1;
}
static size_t leading_whitespaces(fzf_string_t *str) {
size_t whitespaces = 0;
for (size_t i = 0; i < str->size; i++) {
if (!isspace((uint8_t)str->data[i])) {
break;
}
whitespaces++;
}
return whitespaces;
}
static size_t trailing_whitespaces(fzf_string_t *str) {
size_t whitespaces = 0;
for (size_t i = str->size - 1; i >= 0; i--) {
if (!isspace((uint8_t)str->data[i])) {
break;
}
whitespaces++;
}
return whitespaces;
}
static void copy_runes(fzf_string_t *src, fzf_i32_t *destination) {
for (size_t i = 0; i < src->size; i++) {
destination->data[i] = (int32_t)src->data[i];
}
}
static void copy_into_i16(i16_slice_t *src, fzf_i16_t *dest) {
for (size_t i = 0; i < src->size; i++) {
dest->data[i] = src->data[i];
}
}
// char* helpers
static char *trim_whitespace_left(char *str, size_t *len) {
for (size_t i = 0; i < *len; i++) {
if (str[0] == ' ') {
(*len)--;
str++;
} else {
break;
}
}
return str;
}
static bool has_prefix(const char *str, const char *prefix, size_t prefix_len) {
return strncmp(prefix, str, prefix_len) == 0;
}
static bool has_suffix(const char *str, size_t len, const char *suffix,
size_t suffix_len) {
return len >= suffix_len &&
strncmp(slice_str(str, len - suffix_len, len).data, suffix,
suffix_len) == 0;
}
static char *str_replace_char(char *str, char find, char replace) {
char *current_pos = strchr(str, find);
while (current_pos) {
*current_pos = replace;
current_pos = strchr(current_pos, find);
}
return str;
}
static char *str_replace(char *orig, char *rep, char *with) {
if (!orig || !rep || !with) {
return NULL;
}
char *result;
char *ins;
char *tmp;
size_t len_rep = strlen(rep);
size_t len_front = 0;
size_t len_orig = strlen(orig);
size_t len_with = strlen(with);
size_t count = 0;
if (len_rep == 0) {
return NULL;
}
ins = orig;
for (; (tmp = strstr(ins, rep)); ++count) {
ins = tmp + len_rep;
}
tmp = result = (char *)malloc(len_orig + (len_with - len_rep) * count + 1);
if (!result) {
return NULL;
}
while (count--) {
ins = strstr(orig, rep);
len_front = (size_t)(ins - orig);
tmp = strncpy(tmp, orig, len_front) + len_front;
tmp = strcpy(tmp, with) + len_with;
orig += len_front + len_rep;
len_orig -= len_front + len_rep;
}
strncpy(tmp, orig, len_orig);
tmp[len_orig] = 0;
return result;
}
// TODO(conni2461): REFACTOR
static char *str_tolower(char *str, size_t size) {
char *lower_str = (char *)malloc((size + 1) * sizeof(char));
for (size_t i = 0; i < size; i++) {
lower_str[i] = (char)tolower((uint8_t)str[i]);
}
lower_str[size] = '\0';
return lower_str;
}
static int16_t max16(int16_t a, int16_t b) {
return (a > b) ? a : b;
}
static size_t min64u(size_t a, size_t b) {
return (a < b) ? a : b;
}
fzf_position_t *fzf_pos_array(size_t len) {
fzf_position_t *pos = (fzf_position_t *)malloc(sizeof(fzf_position_t));
pos->size = 0;
pos->cap = len;
if (len > 0) {
pos->data = (uint32_t *)malloc(len * sizeof(uint32_t));
} else {
pos->data = NULL;
}
return pos;
}
static void resize_pos(fzf_position_t *pos, size_t add_len, size_t comp) {
if (!pos) {
return;
}
if (pos->size + comp > pos->cap) {
pos->cap += add_len > 0 ? add_len : 1;
pos->data = (uint32_t *)realloc(pos->data, sizeof(uint32_t) * pos->cap);
}
}
static void unsafe_append_pos(fzf_position_t *pos, size_t value) {
resize_pos(pos, pos->cap, 1);
pos->data[pos->size] = value;
pos->size++;
}
static void append_pos(fzf_position_t *pos, size_t value) {
if (pos) {
unsafe_append_pos(pos, value);
}
}
static void insert_range(fzf_position_t *pos, size_t start, size_t end) {
if (!pos) {
return;
}
int32_t diff = ((int32_t)end - (int32_t)start);
if (diff <= 0) {
return;
}
resize_pos(pos, end - start, end - start);
for (size_t k = start; k < end; k++) {
pos->data[pos->size] = k;
pos->size++;
}
}
static fzf_i16_t alloc16(size_t *offset, fzf_slab_t *slab, size_t size) {
if (slab != NULL && slab->I16.cap > *offset + size) {
i16_slice_t slice = slice_i16(slab->I16.data, *offset, (*offset) + size);
*offset = *offset + size;
return (fzf_i16_t){.data = slice.data,
.size = slice.size,
.cap = slice.size,
.allocated = false};
}
int16_t *data = (int16_t *)malloc(size * sizeof(int16_t));
memset(data, 0, size * sizeof(int16_t));
return (fzf_i16_t){
.data = data, .size = size, .cap = size, .allocated = true};
}
static fzf_i32_t alloc32(size_t *offset, fzf_slab_t *slab, size_t size) {
if (slab != NULL && slab->I32.cap > *offset + size) {
i32_slice_t slice = slice_i32(slab->I32.data, *offset, (*offset) + size);
*offset = *offset + size;
return (fzf_i32_t){.data = slice.data,
.size = slice.size,
.cap = slice.size,
.allocated = false};
}
int32_t *data = (int32_t *)malloc(size * sizeof(int32_t));
memset(data, 0, size * sizeof(int32_t));
return (fzf_i32_t){
.data = data, .size = size, .cap = size, .allocated = true};
}
static char_class char_class_of_ascii(char ch) {
if (ch >= 'a' && ch <= 'z') {
return CharLower;
}
if (ch >= 'A' && ch <= 'Z') {
return CharUpper;
}
if (ch >= '0' && ch <= '9') {
return CharNumber;
}
return CharNonWord;
}
// static char_class char_class_of_non_ascii(char ch) {
// return 0;
// }
static char_class char_class_of(char ch) {
return char_class_of_ascii(ch);
// if (ch <= 0x7f) {
// return char_class_of_ascii(ch);
// }
// return char_class_of_non_ascii(ch);
}
static int16_t bonus_for(char_class prev_class, char_class class) {
if (prev_class == CharNonWord && class != CharNonWord) {
return BonusBoundary;
}
if ((prev_class == CharLower && class == CharUpper) ||
(prev_class != CharNumber && class == CharNumber)) {
return BonusCamel123;
}
if (class == CharNonWord) {
return BonusNonWord;
}
return 0;
}
static int16_t bonus_at(fzf_string_t *input, size_t idx) {
if (idx == 0) {
return BonusBoundary;
}
return bonus_for(char_class_of(input->data[idx - 1]),
char_class_of(input->data[idx]));
}
/* TODO(conni2461): maybe just not do this */
static char normalize_rune(char r) {
// TODO(conni2461)
/* if (r < 0x00C0 || r > 0x2184) { */
/* return r; */
/* } */
/* rune n = normalized[r]; */
/* if n > 0 { */
/* return n; */
/* } */
return r;
}
static int32_t try_skip(fzf_string_t *input, bool case_sensitive, byte b,
int32_t from) {
str_slice_t slice = slice_str(input->data, (size_t)from, input->size);
fzf_string_t byte_array = {.data = slice.data, .size = slice.size};
int32_t idx = index_byte(&byte_array, b);
if (idx == 0) {
return from;
}
if (!case_sensitive && b >= 'a' && b <= 'z') {
if (idx > 0) {
str_slice_t tmp = slice_str_right(byte_array.data, (size_t)idx);
byte_array.data = tmp.data;
byte_array.size = tmp.size;
}
int32_t uidx = index_byte(&byte_array, b - (byte)32);
if (uidx >= 0) {
idx = uidx;
}
}
if (idx < 0) {
return -1;
}
return from + idx;
}
static bool is_ascii(const char *runes, size_t size) {
// TODO(conni2461): future use
/* for (size_t i = 0; i < size; i++) { */
/* if (runes[i] >= 256) { */
/* return false; */
/* } */
/* } */
return true;
}
static int32_t ascii_fuzzy_index(fzf_string_t *input, const char *pattern,
size_t size, bool case_sensitive) {
if (!is_ascii(pattern, size)) {
return -1;
}
int32_t first_idx = 0;
int32_t idx = 0;
for (size_t pidx = 0; pidx < size; pidx++) {
idx = try_skip(input, case_sensitive, pattern[pidx], idx);
if (idx < 0) {
return -1;
}
if (pidx == 0 && idx > 0) {
first_idx = idx - 1;
}
idx++;
}
return first_idx;
}
static int32_t calculate_score(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
size_t sidx, size_t eidx, fzf_position_t *pos) {
const size_t M = pattern->size;
size_t pidx = 0;
int32_t score = 0;
int32_t consecutive = 0;
bool in_gap = false;
int16_t first_bonus = 0;
resize_pos(pos, M, M);
int32_t prev_class = CharNonWord;
if (sidx > 0) {
prev_class = char_class_of(text->data[sidx - 1]);
}
for (size_t idx = sidx; idx < eidx; idx++) {
char c = text->data[idx];
int32_t class = char_class_of(c);
if (!case_sensitive) {
/* TODO(conni2461): He does some unicode stuff here, investigate */
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
if (c == pattern->data[pidx]) {
append_pos(pos, idx);
score += ScoreMatch;
int16_t bonus = bonus_for(prev_class, class);
if (consecutive == 0) {
first_bonus = bonus;
} else {
if (bonus == BonusBoundary) {
first_bonus = bonus;
}
bonus = max16(max16(bonus, first_bonus), BonusConsecutive);
}
if (pidx == 0) {
score += (int32_t)(bonus * BonusFirstCharMultiplier);
} else {
score += (int32_t)bonus;
}
in_gap = false;
consecutive++;
pidx++;
} else {
if (in_gap) {
score += ScoreGapExtention;
} else {
score += ScoreGapStart;
}
in_gap = true;
consecutive = 0;
first_bonus = 0;
}
prev_class = class;
}
return score;
}
fzf_result_t fzf_fuzzy_match_v1(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
const size_t M = pattern->size;
const size_t N = text->size;
if (M == 0) {
return (fzf_result_t){0, 0, 0};
}
if (ascii_fuzzy_index(text, pattern->data, M, case_sensitive) < 0) {
return (fzf_result_t){-1, -1, 0};
}
int32_t pidx = 0;
int32_t sidx = -1;
int32_t eidx = -1;
for (size_t idx = 0; idx < N; idx++) {
char c = text->data[idx];
/* TODO(conni2461): Common pattern maybe a macro would be good here */
if (!case_sensitive) {
/* TODO(conni2461): He does some unicode stuff here, investigate */
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
if (c == pattern->data[pidx]) {
if (sidx < 0) {
sidx = (int32_t)idx;
}
pidx++;
if (pidx == M) {
eidx = (int32_t)idx + 1;
break;
}
}
}
if (sidx >= 0 && eidx >= 0) {
size_t start = (size_t)sidx;
size_t end = (size_t)eidx;
pidx--;
for (size_t idx = end - 1; idx >= start; idx--) {
char c = text->data[idx];
if (!case_sensitive) {
/* TODO(conni2461): He does some unicode stuff here, investigate */
c = (char)tolower((uint8_t)c);
}
if (c == pattern->data[pidx]) {
pidx--;
if (pidx < 0) {
start = idx;
break;
}
}
}
int32_t score = calculate_score(case_sensitive, normalize, text, pattern,
start, end, pos);
return (fzf_result_t){(int32_t)start, (int32_t)end, score};
}
return (fzf_result_t){-1, -1, 0};
}
fzf_result_t fzf_fuzzy_match_v2(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
const size_t M = pattern->size;
const size_t N = text->size;
if (M == 0) {
return (fzf_result_t){0, 0, 0};
}
if (slab != NULL && N * M > slab->I16.cap) {
return fzf_fuzzy_match_v1(case_sensitive, normalize, text, pattern, pos,
slab);
}
size_t idx;
{
int32_t tmp_idx = ascii_fuzzy_index(text, pattern->data, M, case_sensitive);
if (tmp_idx < 0) {
return (fzf_result_t){-1, -1, 0};
}
idx = (size_t)tmp_idx;
}
size_t offset16 = 0;
size_t offset32 = 0;
fzf_i16_t h0 = alloc16(&offset16, slab, N);
fzf_i16_t c0 = alloc16(&offset16, slab, N);
// Bonus point for each positions
fzf_i16_t bo = alloc16(&offset16, slab, N);
// The first occurrence of each character in the pattern
fzf_i32_t f = alloc32(&offset32, slab, M);
// Rune array
fzf_i32_t t = alloc32(&offset32, slab, N);
copy_runes(text, &t); // input.CopyRunes(T)
// Phase 2. Calculate bonus for each point
int16_t max_score = 0;
size_t max_score_pos = 0;
size_t pidx = 0;
size_t last_idx = 0;
char pchar0 = pattern->data[0];
char pchar = pattern->data[0];
int16_t prev_h0 = 0;
int32_t prev_class = CharNonWord;
bool in_gap = false;
i32_slice_t t_sub = slice_i32(t.data, idx, t.size); // T[idx:];
i16_slice_t h0_sub =
slice_i16_right(slice_i16(h0.data, idx, h0.size).data, t_sub.size);
i16_slice_t c0_sub =
slice_i16_right(slice_i16(c0.data, idx, c0.size).data, t_sub.size);
i16_slice_t b_sub =
slice_i16_right(slice_i16(bo.data, idx, bo.size).data, t_sub.size);
for (size_t off = 0; off < t_sub.size; off++) {
char_class class;
char c = (char)t_sub.data[off];
class = char_class_of_ascii(c);
if (!case_sensitive && class == CharUpper) {
/* TODO(conni2461): unicode support */
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
t_sub.data[off] = (uint8_t)c;
int16_t bonus = bonus_for(prev_class, class);
b_sub.data[off] = bonus;
prev_class = class;
if (c == pchar) {
if (pidx < M) {
f.data[pidx] = (int32_t)(idx + off);
pidx++;
pchar = pattern->data[min64u(pidx, M - 1)];
}
last_idx = idx + off;
}
if (c == pchar0) {
int16_t score = ScoreMatch + bonus * BonusFirstCharMultiplier;
h0_sub.data[off] = score;
c0_sub.data[off] = 1;
if (M == 1 && (score > max_score)) {
max_score = score;
max_score_pos = idx + off;
if (bonus == BonusBoundary) {
break;
}
}
in_gap = false;
} else {
if (in_gap) {
h0_sub.data[off] = max16(prev_h0 + ScoreGapExtention, 0);
} else {
h0_sub.data[off] = max16(prev_h0 + ScoreGapStart, 0);
}
c0_sub.data[off] = 0;
in_gap = true;
}
prev_h0 = h0_sub.data[off];
}
if (pidx != M) {
free_alloc(t);
free_alloc(f);
free_alloc(bo);
free_alloc(c0);
free_alloc(h0);
return (fzf_result_t){-1, -1, 0};
}
if (M == 1) {
free_alloc(t);
free_alloc(f);
free_alloc(bo);
free_alloc(c0);
free_alloc(h0);
fzf_result_t res = {(int32_t)max_score_pos, (int32_t)max_score_pos + 1,
max_score};
append_pos(pos, max_score_pos);
return res;
}
size_t f0 = (size_t)f.data[0];
size_t width = last_idx - f0 + 1;
fzf_i16_t h = alloc16(&offset16, slab, width * M);
{
i16_slice_t h0_tmp_slice = slice_i16(h0.data, f0, last_idx + 1);
copy_into_i16(&h0_tmp_slice, &h);
}
fzf_i16_t c = alloc16(&offset16, slab, width * M);
{
i16_slice_t c0_tmp_slice = slice_i16(c0.data, f0, last_idx + 1);
copy_into_i16(&c0_tmp_slice, &c);
}
i32_slice_t f_sub = slice_i32(f.data, 1, f.size);
str_slice_t p_sub =
slice_str_right(slice_str(pattern->data, 1, M).data, f_sub.size);
for (size_t off = 0; off < f_sub.size; off++) {
size_t foff = (size_t)f_sub.data[off];
pchar = p_sub.data[off];
pidx = off + 1;
size_t row = pidx * width;
in_gap = false;
t_sub = slice_i32(t.data, foff, last_idx + 1);
b_sub = slice_i16_right(slice_i16(bo.data, foff, bo.size).data, t_sub.size);
i16_slice_t c_sub = slice_i16_right(
slice_i16(c.data, row + foff - f0, c.size).data, t_sub.size);
i16_slice_t c_diag = slice_i16_right(
slice_i16(c.data, row + foff - f0 - 1 - width, c.size).data,
t_sub.size);
i16_slice_t h_sub = slice_i16_right(
slice_i16(h.data, row + foff - f0, h.size).data, t_sub.size);
i16_slice_t h_diag = slice_i16_right(
slice_i16(h.data, row + foff - f0 - 1 - width, h.size).data,
t_sub.size);
i16_slice_t h_left = slice_i16_right(
slice_i16(h.data, row + foff - f0 - 1, h.size).data, t_sub.size);
h_left.data[0] = 0;
for (size_t j = 0; j < t_sub.size; j++) {
char ch = (char)t_sub.data[j];
size_t col = j + foff;
int16_t s1 = 0;
int16_t s2 = 0;
int16_t consecutive = 0;
if (in_gap) {
s2 = h_left.data[j] + ScoreGapExtention;
} else {
s2 = h_left.data[j] + ScoreGapStart;
}
if (pchar == ch) {
s1 = h_diag.data[j] + ScoreMatch;
int16_t b = b_sub.data[j];
consecutive = c_diag.data[j] + 1;
if (b == BonusBoundary) {
consecutive = 1;
} else if (consecutive > 1) {
b = max16(b, max16(BonusConsecutive,
bo.data[col - ((size_t)consecutive) + 1]));
}
if (s1 + b < s2) {
s1 += b_sub.data[j];
consecutive = 0;
} else {
s1 += b;
}
}
c_sub.data[j] = consecutive;
in_gap = s1 < s2;
int16_t score = max16(max16(s1, s2), 0);
if (pidx == M - 1 && (score > max_score)) {
max_score = score;
max_score_pos = col;
}
h_sub.data[j] = score;
}
}
resize_pos(pos, M, M);
size_t j = max_score_pos;
if (pos) {
size_t i = M - 1;
bool prefer_match = true;
for (;;) {
size_t ii = i * width;
size_t j0 = j - f0;
int16_t s = h.data[ii + j0];
int16_t s1 = 0;
int16_t s2 = 0;
if (i > 0 && j >= f.data[i]) {
s1 = h.data[ii - width + j0 - 1];
}
if (j > f.data[i]) {
s2 = h.data[ii + j0 - 1];
}
if (s > s1 && (s > s2 || (s == s2 && prefer_match))) {
unsafe_append_pos(pos, j);
if (i == 0) {
break;
}
i--;
}
prefer_match = c.data[ii + j0] > 1 || (ii + width + j0 + 1 < c.size &&
c.data[ii + width + j0 + 1] > 0);
j--;
}
}
free_alloc(h);
free_alloc(c);
free_alloc(t);
free_alloc(f);
free_alloc(bo);
free_alloc(c0);
free_alloc(h0);
return (fzf_result_t){(int32_t)j, (int32_t)max_score_pos + 1,
(int32_t)max_score};
}
fzf_result_t fzf_exact_match_naive(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
const size_t M = pattern->size;
const size_t N = text->size;
if (M == 0) {
return (fzf_result_t){0, 0, 0};
}
if (N < M) {
return (fzf_result_t){-1, -1, 0};
}
if (ascii_fuzzy_index(text, pattern->data, M, case_sensitive) < 0) {
return (fzf_result_t){-1, -1, 0};
}
size_t pidx = 0;
int32_t best_pos = -1;
int16_t bonus = 0;
int16_t best_bonus = -1;
for (size_t idx = 0; idx < N; idx++) {
char c = text->data[idx];
if (!case_sensitive) {
/* TODO(conni2461): He does some unicode stuff here, investigate */
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
if (c == pattern->data[pidx]) {
if (pidx == 0) {
bonus = bonus_at(text, idx);
}
pidx++;
if (pidx == M) {
if (bonus > best_bonus) {
best_pos = (int32_t)idx;
best_bonus = bonus;
}
if (bonus == BonusBoundary) {
break;
}
idx -= pidx - 1;
pidx = 0;
bonus = 0;
}
} else {
idx -= pidx;
pidx = 0;
bonus = 0;
}
}
if (best_pos >= 0) {
size_t bp = (size_t)best_pos;
size_t sidx = bp - M + 1;
size_t eidx = bp + 1;
int32_t score = calculate_score(case_sensitive, normalize, text, pattern,
sidx, eidx, NULL);
insert_range(pos, sidx, eidx);
return (fzf_result_t){(int32_t)sidx, (int32_t)eidx, score};
}
return (fzf_result_t){-1, -1, 0};
}
fzf_result_t fzf_prefix_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
const size_t M = pattern->size;
if (M == 0) {
return (fzf_result_t){0, 0, 0};
}
size_t trimmed_len = 0;
/* TODO(conni2461): i feel this is wrong */
if (!isspace((uint8_t)pattern->data[0])) {
trimmed_len = leading_whitespaces(text);
}
if (text->size - trimmed_len < M) {
return (fzf_result_t){-1, -1, 0};
}
for (size_t i = 0; i < M; i++) {
char c = text->data[trimmed_len + i];
if (!case_sensitive) {
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
if (c != pattern->data[i]) {
return (fzf_result_t){-1, -1, 0};
}
}
size_t start = trimmed_len;
size_t end = trimmed_len + M;
int32_t score = calculate_score(case_sensitive, normalize, text, pattern,
start, end, NULL);
insert_range(pos, start, end);
return (fzf_result_t){(int32_t)start, (int32_t)end, score};
}
fzf_result_t fzf_suffix_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
size_t trimmed_len = text->size;
const size_t M = pattern->size;
/* TODO(conni2461): i think this is wrong */
if (M == 0 || !isspace((uint8_t)pattern->data[M - 1])) {
trimmed_len -= trailing_whitespaces(text);
}
if (M == 0) {
return (fzf_result_t){(int32_t)trimmed_len, (int32_t)trimmed_len, 0};
}
size_t diff = trimmed_len - M;
if (diff < 0) {
return (fzf_result_t){-1, -1, 0};
}
for (size_t idx = 0; idx < M; idx++) {
char c = text->data[idx + diff];
if (!case_sensitive) {
c = (char)tolower((uint8_t)c);
}
if (normalize) {
c = normalize_rune(c);
}
if (c != pattern->data[idx]) {
return (fzf_result_t){-1, -1, 0};
}
}
size_t start = trimmed_len - M;
size_t end = trimmed_len;
int32_t score = calculate_score(case_sensitive, normalize, text, pattern,
start, end, NULL);
insert_range(pos, start, end);
return (fzf_result_t){(int32_t)start, (int32_t)end, score};
}
fzf_result_t fzf_equal_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab) {
const size_t M = pattern->size;
if (M == 0) {
return (fzf_result_t){-1, -1, 0};
}
size_t trimmed_len = leading_whitespaces(text);
size_t trimmed_end_len = trailing_whitespaces(text);
if ((text->size - trimmed_len - trimmed_end_len) != M) {
return (fzf_result_t){-1, -1, 0};
}
bool match = true;
if (normalize) {
// TODO(conni2461): to rune
for (size_t idx = 0; idx < M; idx++) {
char pchar = pattern->data[idx];
char c = text->data[trimmed_len + idx];
if (!case_sensitive) {
c = (char)tolower((uint8_t)c);
}
if (normalize_rune(c) != normalize_rune(pchar)) {
match = false;
break;
}
}
} else {
// TODO(conni2461): to rune
for (size_t idx = 0; idx < M; idx++) {
char pchar = pattern->data[idx];
char c = text->data[trimmed_len + idx];
if (!case_sensitive) {
c = (char)tolower((uint8_t)c);
}
if (c != pchar) {
match = false;
break;
}
}
}
if (match) {
insert_range(pos, trimmed_len, trimmed_len + M);
return (fzf_result_t){(int32_t)trimmed_len,
((int32_t)trimmed_len + (int32_t)M),
(ScoreMatch + BonusBoundary) * (int32_t)M +
(BonusFirstCharMultiplier - 1) * BonusBoundary};
}
return (fzf_result_t){-1, -1, 0};
}
static void append_set(fzf_term_set_t *set, fzf_term_t value) {
if (set->cap == 0) {
set->cap = 1;
set->ptr = (fzf_term_t *)malloc(sizeof(fzf_term_t));
} else if (set->size + 1 > set->cap) {
set->cap *= 2;
set->ptr = realloc(set->ptr, sizeof(fzf_term_t) * set->cap);
}
set->ptr[set->size] = value;
set->size++;
}
static void append_pattern(fzf_pattern_t *pattern, fzf_term_set_t *value) {
if (pattern->cap == 0) {
pattern->cap = 1;
pattern->ptr = (fzf_term_set_t **)malloc(sizeof(fzf_term_set_t *));
} else if (pattern->size + 1 > pattern->cap) {
pattern->cap *= 2;
pattern->ptr =
realloc(pattern->ptr, sizeof(fzf_term_set_t *) * pattern->cap);
}
pattern->ptr[pattern->size] = value;
pattern->size++;
}
#define CALL_ALG(term, normalize, input, pos, slab) \
term->fn((term)->case_sensitive, normalize, &(input), \
(fzf_string_t *)(term)->text, pos, slab)
// TODO(conni2461): REFACTOR
/* assumption (maybe i change that later)
* - always v2 alg
* - bool extended always true (thats the whole point of this isn't it)
*/
fzf_pattern_t *fzf_parse_pattern(fzf_case_types case_mode, bool normalize,
char *pattern, bool fuzzy) {
fzf_pattern_t *pat_obj = (fzf_pattern_t *)malloc(sizeof(fzf_pattern_t));
memset(pat_obj, 0, sizeof(*pat_obj));
size_t pat_len = strlen(pattern);
if (pat_len == 0) {
return pat_obj;
}
pattern = trim_whitespace_left(pattern, &pat_len);
while (has_suffix(pattern, pat_len, " ", 1) &&
!has_suffix(pattern, pat_len, "\\ ", 2)) {
pattern[pat_len - 1] = 0;
pat_len--;
}
char *pattern_copy = str_replace(pattern, "\\ ", "\t");
const char *delim = " ";
char *ptr = strtok(pattern_copy, delim);
fzf_term_set_t *set = (fzf_term_set_t *)malloc(sizeof(fzf_term_set_t));
memset(set, 0, sizeof(*set));
bool switch_set = false;
bool after_bar = false;
while (ptr != NULL) {
fzf_algo_t fn = fzf_fuzzy_match_v2;
bool inv = false;
size_t len = strlen(ptr);
str_replace_char(ptr, '\t', ' ');
char *text = strdup(ptr);
char *og_str = text;
char *lower_text = str_tolower(text, len);
bool case_sensitive =
case_mode == CaseRespect ||
(case_mode == CaseSmart && strcmp(text, lower_text) != 0);
if (!case_sensitive) {
SFREE(text);
text = lower_text;
og_str = lower_text;
} else {
SFREE(lower_text);
}
if (!fuzzy) {
fn = fzf_exact_match_naive;
}
if (set->size > 0 && !after_bar && strcmp(text, "|") == 0) {
switch_set = false;
after_bar = true;
ptr = strtok(NULL, delim);
SFREE(og_str);
continue;
}
after_bar = false;
if (has_prefix(text, "!", 1)) {
inv = true;
fn = fzf_exact_match_naive;
text++;
len--;
}
if (strcmp(text, "$") != 0 && has_suffix(text, len, "$", 1)) {
fn = fzf_suffix_match;
text[len - 1] = 0;
len--;
}
if (has_prefix(text, "'", 1)) {
if (fuzzy && !inv) {
fn = fzf_exact_match_naive;
text++;
len--;
} else {
fn = fzf_fuzzy_match_v2;
text++;
len--;
}
} else if (has_prefix(text, "^", 1)) {
if (fn == fzf_suffix_match) {
fn = fzf_equal_match;
} else {
fn = fzf_prefix_match;
}
text++;
len--;
}
if (len > 0) {
if (switch_set) {
append_pattern(pat_obj, set);
set = (fzf_term_set_t *)malloc(sizeof(fzf_term_set_t));
set->cap = 0;
set->size = 0;
}
fzf_string_t *text_ptr = (fzf_string_t *)malloc(sizeof(fzf_string_t));
text_ptr->data = text;
text_ptr->size = len;
append_set(set, (fzf_term_t){.fn = fn,
.inv = inv,
.ptr = og_str,
.text = text_ptr,
.case_sensitive = case_sensitive});
switch_set = true;
} else {
SFREE(og_str);
}
ptr = strtok(NULL, delim);
}
if (set->size > 0) {
append_pattern(pat_obj, set);
} else {
SFREE(set->ptr);
SFREE(set);
}
bool only = true;
for (size_t i = 0; i < pat_obj->size; i++) {
fzf_term_set_t *term_set = pat_obj->ptr[i];
if (term_set->size > 1) {
only = false;
break;
}
if (term_set->ptr[0].inv == false) {
only = false;
break;
}
}
pat_obj->only_inv = only;
SFREE(pattern_copy);
return pat_obj;
}
void fzf_free_pattern(fzf_pattern_t *pattern) {
if (pattern->ptr) {
for (size_t i = 0; i < pattern->size; i++) {
fzf_term_set_t *term_set = pattern->ptr[i];
for (size_t j = 0; j < term_set->size; j++) {
fzf_term_t *term = &term_set->ptr[j];
free(term->ptr);
free(term->text);
}
free(term_set->ptr);
free(term_set);
}
free(pattern->ptr);
}
SFREE(pattern);
}
int32_t fzf_get_score(const char *text, fzf_pattern_t *pattern,
fzf_slab_t *slab) {
// If the pattern is an empty string then pattern->ptr will be NULL and we
// basically don't want to filter. Return 1 for telescope
if (pattern->ptr == NULL) {
return 1;
}
fzf_string_t input = {.data = text, .size = strlen(text)};
if (pattern->only_inv) {
int final = 0;
for (size_t i = 0; i < pattern->size; i++) {
fzf_term_set_t *term_set = pattern->ptr[i];
fzf_term_t *term = &term_set->ptr[0];
final += CALL_ALG(term, false, input, NULL, slab).score;
}
return (final > 0) ? 0 : 1;
}
int32_t total_score = 0;
for (size_t i = 0; i < pattern->size; i++) {
fzf_term_set_t *term_set = pattern->ptr[i];
int32_t current_score = 0;
bool matched = false;
for (size_t j = 0; j < term_set->size; j++) {
fzf_term_t *term = &term_set->ptr[j];
fzf_result_t res = CALL_ALG(term, false, input, NULL, slab);
if (res.start >= 0) {
if (term->inv) {
continue;
}
current_score = res.score;
matched = true;
break;
}
if (term->inv) {
current_score = 0;
matched = true;
}
}
if (matched) {
total_score += current_score;
} else {
total_score = 0;
break;
}
}
return total_score;
}
fzf_position_t *fzf_get_positions(const char *text, fzf_pattern_t *pattern,
fzf_slab_t *slab) {
// If the pattern is an empty string then pattern->ptr will be NULL and we
// basically don't want to filter. Return 1 for telescope
if (pattern->ptr == NULL) {
return NULL;
}
fzf_string_t input = {.data = text, .size = strlen(text)};
fzf_position_t *all_pos = fzf_pos_array(0);
for (size_t i = 0; i < pattern->size; i++) {
fzf_term_set_t *term_set = pattern->ptr[i];
bool matched = false;
for (size_t j = 0; j < term_set->size; j++) {
fzf_term_t *term = &term_set->ptr[j];
if (term->inv) {
// If we have an inverse term we need to check if we have a match, but
// we are not interested in the positions (for highlights) so to speed
// this up we can pass in NULL here and don't calculate the positions
fzf_result_t res = CALL_ALG(term, false, input, NULL, slab);
if (res.start < 0) {
matched = true;
}
continue;
}
fzf_result_t res = CALL_ALG(term, false, input, all_pos, slab);
if (res.start >= 0) {
matched = true;
break;
}
}
if (!matched) {
fzf_free_positions(all_pos);
return NULL;
}
}
return all_pos;
}
void fzf_free_positions(fzf_position_t *pos) {
if (pos) {
SFREE(pos->data);
free(pos);
}
}
fzf_slab_t *fzf_make_slab(fzf_slab_config_t config) {
fzf_slab_t *slab = (fzf_slab_t *)malloc(sizeof(fzf_slab_t));
memset(slab, 0, sizeof(*slab));
slab->I16.data = (int16_t *)malloc(config.size_16 * sizeof(int16_t));
memset(slab->I16.data, 0, config.size_16 * sizeof(*slab->I16.data));
slab->I16.cap = config.size_16;
slab->I16.size = 0;
slab->I16.allocated = true;
slab->I32.data = (int32_t *)malloc(config.size_32 * sizeof(int32_t));
memset(slab->I32.data, 0, config.size_32 * sizeof(*slab->I32.data));
slab->I32.cap = config.size_32;
slab->I32.size = 0;
slab->I32.allocated = true;
return slab;
}
fzf_slab_t *fzf_make_default_slab(void) {
return fzf_make_slab((fzf_slab_config_t){(size_t)100 * 1024, 2048});
}
void fzf_free_slab(fzf_slab_t *slab) {
if (slab) {
free(slab->I16.data);
free(slab->I32.data);
free(slab);
}
}
================================================
FILE: fzf/fzf.h
================================================
#ifndef FZF_H_
#define FZF_H_
#include
#include
#include
typedef struct {
int16_t *data;
size_t size;
size_t cap;
bool allocated;
} fzf_i16_t;
typedef struct {
int32_t *data;
size_t size;
size_t cap;
bool allocated;
} fzf_i32_t;
typedef struct {
uint32_t *data;
size_t size;
size_t cap;
} fzf_position_t;
typedef struct {
int32_t start;
int32_t end;
int32_t score;
} fzf_result_t;
typedef struct {
fzf_i16_t I16;
fzf_i32_t I32;
} fzf_slab_t;
typedef struct {
size_t size_16;
size_t size_32;
} fzf_slab_config_t;
typedef struct {
const char *data;
size_t size;
} fzf_string_t;
typedef fzf_result_t (*fzf_algo_t)(bool, bool, fzf_string_t *, fzf_string_t *,
fzf_position_t *, fzf_slab_t *);
typedef enum { CaseSmart = 0, CaseIgnore, CaseRespect } fzf_case_types;
typedef struct {
fzf_algo_t fn;
bool inv;
char *ptr;
void *text;
bool case_sensitive;
} fzf_term_t;
typedef struct {
fzf_term_t *ptr;
size_t size;
size_t cap;
} fzf_term_set_t;
typedef struct {
fzf_term_set_t **ptr;
size_t size;
size_t cap;
bool only_inv;
} fzf_pattern_t;
fzf_result_t fzf_fuzzy_match_v1(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
fzf_result_t fzf_fuzzy_match_v2(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
fzf_result_t fzf_exact_match_naive(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
fzf_result_t fzf_prefix_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
fzf_result_t fzf_suffix_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
fzf_result_t fzf_equal_match(bool case_sensitive, bool normalize,
fzf_string_t *text, fzf_string_t *pattern,
fzf_position_t *pos, fzf_slab_t *slab);
/* interface */
fzf_pattern_t *fzf_parse_pattern(fzf_case_types case_mode, bool normalize,
char *pattern, bool fuzzy);
void fzf_free_pattern(fzf_pattern_t *pattern);
int32_t fzf_get_score(const char *text, fzf_pattern_t *pattern,
fzf_slab_t *slab);
fzf_position_t *fzf_pos_array(size_t len);
fzf_position_t *fzf_get_positions(const char *text, fzf_pattern_t *pattern,
fzf_slab_t *slab);
void fzf_free_positions(fzf_position_t *pos);
fzf_slab_t *fzf_make_slab(fzf_slab_config_t config);
fzf_slab_t *fzf_make_default_slab(void);
void fzf_free_slab(fzf_slab_t *slab);
#endif // FZF_H_
================================================
FILE: last_version.txt
================================================
435
================================================
FILE: linuxdeploy_build_and_release.sh
================================================
#!/usr/bin/env bash
set -e
if [ -z ${MAKE_PARALLEL+x} ]; then export MAKE_PARALLEL=$(nproc); else echo "MAKE_PARALLEL defined"; fi
echo "MAKE_PARALLEL set to $MAKE_PARALLEL"
# download linuxdeployqt if not exists
if [[ ! -f ./linuxdeploy-x86_64.AppImage ]]; then
# the continuous version is not working for some reason
# wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
# wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20240109-1/linuxdeploy-x86_64.AppImage
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/1-alpha-20240109-1/linuxdeploy-plugin-qt-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-plugin-qt-x86_64.AppImage
fi
cd mupdf
make USE_SYSTEM_HARFBUZZ=yes -j$MAKE_PARALLEL
cd ..
if [ -z ${QMAKE+x} ]; then
QMAKE=qmake
fi
if [[ $1 == console ]]; then
$QMAKE "CONFIG+=linux_app_image console" pdf_viewer_build_config.pro
else
$QMAKE "CONFIG+=linux_app_image" pdf_viewer_build_config.pro
fi
rm -rf sioyek-release 2> /dev/null
make install INSTALL_ROOT=sioyek-release -j$MAKE_PARALLEL
cp pdf_viewer/prefs.config sioyek-release/usr/bin/prefs.config
cp pdf_viewer/prefs_user.config sioyek-release/usr/share/prefs_user.config
cp pdf_viewer/keys.config sioyek-release/usr/bin/keys.config
cp pdf_viewer/keys_user.config sioyek-release/usr/share/keys_user.config
cp -r pdf_viewer/shaders sioyek-release/usr/bin/shaders
cp tutorial.pdf sioyek-release/usr/bin/tutorial.pdf
QML_SOURCES_PATHS=./pdf_viewer/touchui ./linuxdeploy-x86_64.AppImage --appdir sioyek-release --plugin qt --output appimage
# ./linuxdeployqt-continuous-x86_64.AppImage ./sioyek-release/usr/share/applications/sioyek.desktop -qmldir=./pdf_viewer/touchui -appimage
# ./linuxdeployqt-continuous-x86_64.AppImage ./sioyek-release/usr/share/applications/sioyek.desktop -appimage
# mv Sioyek-* Sioyek-x86_64.AppImage
zip sioyek-release-linux.zip Sioyek-x86_64.AppImage
================================================
FILE: pdf_viewer/.gitignore
================================================
data/*
x64/*
release/*
Debug/*
.vscode/*
Generated
*.pdf
*.pdb
*.db
*.aps
.qmake.stash
last_document_path.txt
imgui.ini
*.jar
last_document_path.txt
*.o
================================================
FILE: pdf_viewer/OpenWithApplication.cpp
================================================
#include
bool OpenWithApplication::event(QEvent* event) {
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* openEvent = static_cast(event);
emit file_ready(openEvent->file());
}
return QApplication::event(event);
}
================================================
FILE: pdf_viewer/OpenWithApplication.h
================================================
#ifndef OPEN_WITH_APP_H
#define OPEN_WITH_APP_H
#include
#include
class OpenWithApplication : public QApplication
{
Q_OBJECT
public:
OpenWithApplication(int& argc, char** argv)
: QApplication(argc, argv)
{
}
signals:
void file_ready(const QString& file_name);
protected:
bool event(QEvent* event) override;
};
#endif // OPEN_WITH_APP_H
================================================
FILE: pdf_viewer/RunGuard.cpp
================================================
#ifndef SIOYEK_ANDROID
#include "RunGuard.h"
#include
#include
#include
namespace
{
QString generateKeyHash(const QString& key, const QString& salt)
{
QByteArray data;
data.append(key.toUtf8());
data.append(salt.toUtf8());
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
return data;
}
}
RunGuard::RunGuard(const QString& key) : QObject{},
key(key),
memoryKey(generateKeyHash(key, "_sharedMemKey"))
{
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
memory = new QSharedMemory{ memoryKey };
memory->attach();
delete memory;
// Guarantee process safe behaviour with a shared memory block.
memory = new QSharedMemory(memoryKey);
// Creates a shared memory segment then attaches to it with the given
// access mode and returns true.
// If a shared memory segment identified by the key already exists, the
// attach operation is not performed and false is returned.
//qDebug() << "Creating shared memory block...";
bool isPrimary = false;
if (memory->create(sizeof(quint64))) {
//qDebug() << "Shared memory created: this is the primary application.";
isPrimary = true;
}
else {
//qDebug() << "Shared memory already exists: this is a secondary application.";
//qDebug() << "Secondary application attaching to shared memory block...";
if (!memory->attach()) {
qCritical() << "Secondary application cannot attach to shared memory block.";
QCoreApplication::exit();
}
//qDebug() << "Secondary application successfully attached to shared memory block.";
}
memory->lock();
if (isPrimary) { // Start primary server.
//qDebug() << "Starting IPC server...";
QLocalServer::removeServer(key);
server = new QLocalServer;
server->setSocketOptions(QLocalServer::UserAccessOption);
if (server->listen(key)) {
//qDebug() << "IPC server started.";
}
else {
qCritical() << "Cannot start IPC server.";
QCoreApplication::exit();
}
QObject::connect(server, &QLocalServer::newConnection,
this, &RunGuard::onNewConnection);
}
memory->unlock();
}
RunGuard::~RunGuard()
{
bool was_server = false;
memory->lock();
if (server) {
was_server = true;
server->close();
delete server;
server = nullptr;
}
memory->unlock();
if (was_server){
delete memory;
}
}
bool RunGuard::isPrimary()
{
return server != nullptr;
}
bool RunGuard::isSecondary()
{
return server == nullptr;
}
void RunGuard::onNewConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
QObject::connect(socket, &QLocalSocket::disconnected, this,
[this, socket]() {
if (on_delete) {
on_delete(socket);
}
socket->deleteLater();
}
);
QObject::connect(socket, &QLocalSocket::readyRead, this, [socket, this]() {
readMessage(socket);
});
}
void RunGuard::readMessage(QLocalSocket* socket)
{
QByteArray data = socket->readAll();
emit messageReceived(data, socket);
}
std::string RunGuard::sendMessage(const QByteArray& message, bool wait)
{
QLocalSocket socket;
socket.connectToServer(key, QLocalSocket::ReadWrite);
socket.waitForConnected();
if (socket.state() == QLocalSocket::ConnectedState) {
if (socket.state() == QLocalSocket::ConnectedState) {
socket.write(message);
if (socket.waitForBytesWritten()) {
if (wait) {
socket.waitForReadyRead();
std::string response = socket.readAll().toStdString();
return response;
}
//qCritical() << "Secondary application sent message to IPC server.";
}
}
}
else {
qCritical() << "Secondary application cannot connect to IPC server.";
qCritical() << "Socket error: " << socket.error();
QCoreApplication::exit();
}
return "";
}
#endif // SIOYEK_ANDROID
================================================
FILE: pdf_viewer/RunGuard.h
================================================
#ifndef SIOYEK_ANDROID
#ifndef SINGLE_INSTANCE_GUARD_H
#define SINGLE_INSTANCE_GUARD_H
#include
#include
#include
#include
/**
* This is an control to guarantee that only one application instance exists at
* any time.
* It uses shared memory to check that no more than one instance is running at
* the same time and also it uses Inter Process Communication (IPC) for a
* secondary application instance to send parameters to the primary application
* instance before quitting.
* An Application must be contructed before the control for signals-slot
* communication to work.
*
* Usage example:
*
* int main(int argc, char *argv[])
* {
* QApplication app{argc, argv};
*
* ...
*
* RunGuard guard{"Lentigram"};
* if (guard.isPrimary()) {
* QObject::connect(
* &guard,
* &RunGuard::messageReceived, [this](const QByteArray &message) {
*
* ...process message coming from secondary application...
*
* qDebug() << message;
* }
* );
* } else {
* guard.sendMessage(app.arguments().join(' ').toUtf8());
* return 0;
* }
*
* ...
*
* app.exec();
* }
*
* This code is inspired by the following:
* https://stackoverflow.com/questions/5006547/qt-best-practice-for-a-single-instance-app-protection
* https://github.com/itay-grudev/SingleApplication
*/
class RunGuard : public QObject
{
Q_OBJECT
public:
std::function on_delete;
explicit RunGuard(const QString& key);
~RunGuard();
bool isPrimary();
bool isSecondary();
std::string sendMessage(const QByteArray& message, bool wait=false);
signals:
void messageReceived(const QByteArray& message, QLocalSocket* socket);
private slots:
void onNewConnection();
private:
const QString key;
const QString sharedMemLockKey;
const QString memoryKey;
QSharedMemory* memory;
QLocalServer* server = nullptr;
void readMessage(QLocalSocket* socket);
};
#endif // SINGLE_INSTANCE_GUARD_H
#endif // SIOYEK_ANDROID
================================================
FILE: pdf_viewer/book.cpp
================================================
#include "book.h"
#include "utils.h"
#include "document.h"
extern float BOOKMARK_RECT_SIZE;
bool operator==(const DocumentViewState& lhs, const DocumentViewState& rhs)
{
return (lhs.book_state.offset_x == rhs.book_state.offset_x) &&
(lhs.book_state.offset_y == rhs.book_state.offset_y) &&
(lhs.book_state.zoom_level == rhs.book_state.zoom_level) &&
(lhs.document_path == rhs.document_path);
}
bool operator==(const CachedPageData& lhs, const CachedPageData& rhs) {
if (lhs.doc != rhs.doc) return false;
if (lhs.page != rhs.page) return false;
if (lhs.zoom_level != rhs.zoom_level) return false;
return true;
}
Portal Portal::with_src_offset(float src_offset)
{
Portal res = Portal();
res.src_offset_y = src_offset;
return res;
}
QJsonObject Mark::to_json(std::string doc_checksum) const
{
QJsonObject res;
res["y_offset"] = y_offset;
res["symbol"] = symbol;
add_metadata_to_json(res);
return res;
}
void Annotation::add_metadata_to_json(QJsonObject& obj) const {
obj["creation_time"] = QString::fromStdString(creation_time);
obj["modification_time"] = QString::fromStdString(modification_time);
obj["uuid"] = QString::fromStdString(uuid);
}
void Annotation::load_metadata_from_json(const QJsonObject& obj) {
if (obj.contains("creation_time")) {
creation_time = obj["creation_time"].toString().toStdString();
}
if (obj.contains("modification_time")) {
modification_time = obj["modification_time"].toString().toStdString();
}
if (obj.contains("uuid")) {
uuid = obj["uuid"].toString().toStdString();
}
else {
uuid = utf8_encode(new_uuid());
}
}
std::vector> Annotation::to_tuples() {
std::vector> res;
add_to_tuples(res);
res.push_back({ "uuid", QString::fromStdString(uuid) });
res.push_back({ "creation_time", QString::fromStdString(creation_time) });
res.push_back({ "modification_time", QString::fromStdString(modification_time) });
return res;
}
QDateTime Annotation::get_creation_datetime() const {
return QDateTime::fromString(QString::fromStdString(creation_time), "yyyy-MM-dd HH:mm:ss");
}
QDateTime Annotation::get_modification_datetime() const {
return QDateTime::fromString(QString::fromStdString(modification_time), "yyyy-MM-dd HH:mm:ss");
}
void Annotation::update_creation_time() {
creation_time = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss").toStdString();
update_modification_time();
}
void Annotation::update_modification_time() {
modification_time = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss").toStdString();
}
void Mark::from_json(const QJsonObject& json_object)
{
y_offset = json_object["y_offset"].toDouble();
symbol = static_cast(json_object["symbol"].toInt());
load_metadata_from_json(json_object);
}
void Mark::add_to_tuples(std::vector>& tuples) {
tuples.push_back({ "offset_y", y_offset });
tuples.push_back({ "symbol", QChar(symbol) });
}
QJsonObject BookMark::to_json(std::string doc_checksum) const
{
QJsonObject res;
res["y_offset"] = y_offset_;
res["description"] = QString::fromStdWString(description);
res["begin_x"] = begin_x;
res["begin_y"] = begin_y;
res["end_x"] = end_x;
res["end_y"] = end_y;
if (is_freetext()) {
res["color_red"] = color[0];
res["color_green"] = color[1];
res["color_blue"] = color[2];
res["font_size"] = font_size;
res["font_face"] = QString::fromStdWString(font_face);
}
add_metadata_to_json(res);
return res;
}
void BookMark::add_to_tuples(std::vector>& tuples) {
tuples.push_back({ "offset_y", y_offset_ });
tuples.push_back({ "desc", QString::fromStdWString(description) });
tuples.push_back({ "begin_x", begin_x });
tuples.push_back({ "begin_y", begin_y });
tuples.push_back({ "end_x", end_x });
tuples.push_back({ "end_y", end_y });
tuples.push_back({ "color_red", color[0] });
tuples.push_back({ "color_green", color[1] });
tuples.push_back({ "color_blue", color[2] });
tuples.push_back({ "font_size", font_size });
tuples.push_back({ "font_face", QString::fromStdWString(font_face) });
}
void BookMark::from_json(const QJsonObject& json_object)
{
y_offset_ = json_object["y_offset"].toDouble();
description = json_object["description"].toString().toStdWString();
begin_x = json_object["begin_x"].toDouble();
begin_y = json_object["begin_y"].toDouble();
end_x = json_object["end_x"].toDouble();
end_y = json_object["end_y"].toDouble();
if (json_object.contains("color_red")) {
color[0] = json_object["color_red"].toDouble();
color[1] = json_object["color_green"].toDouble();
color[2] = json_object["color_blue"].toDouble();
font_size = json_object["font_size"].toDouble();
font_face = json_object["font_face"].toString().toStdWString();
}
load_metadata_from_json(json_object);
}
bool BookMark::is_freetext() const {
return (begin_y > -1) && (end_y > -1);
}
std::optional BookMark::get_type() const{
if (is_box()) {
if (description.size() > 1) {
return description[1];
}
}
return {};
}
bool BookMark::is_box() const {
if (description.size() > 0) {
return description[0] == '#';
}
return false;
}
bool BookMark::is_marked() const {
return (begin_y > -1) && (end_y == -1);
}
QJsonObject Highlight::to_json(std::string doc_checksum) const
{
QJsonObject res;
res["selection_begin_x"] = selection_begin.x;
res["selection_begin_y"] = selection_begin.y;
res["selection_end_x"] = selection_end.x;
res["selection_end_y"] = selection_end.y;
res["description"] = QString::fromStdWString(description);
res["text_annot"] = QString::fromStdWString(text_annot);
res["type"] = type;
add_metadata_to_json(res);
return res;
}
void Highlight::add_to_tuples(std::vector>& tuples) {
tuples.push_back({ "begin_x", selection_begin.x });
tuples.push_back({ "begin_y", selection_begin.y });
tuples.push_back({ "end_x", selection_end.x });
tuples.push_back({ "end_y", selection_end.y });
tuples.push_back({ "desc", QString::fromStdWString(description) });
tuples.push_back({ "text_annot", QString::fromStdWString(text_annot) });
tuples.push_back({ "type", QChar(type) });
}
void Highlight::from_json(const QJsonObject& json_object)
{
selection_begin.x = json_object["selection_begin_x"].toDouble();
selection_begin.y = json_object["selection_begin_y"].toDouble();
selection_end.x = json_object["selection_end_x"].toDouble();
selection_end.y = json_object["selection_end_y"].toDouble();
description = json_object["description"].toString().toStdWString();
type = static_cast(json_object["type"].toInt());
load_metadata_from_json(json_object);
}
QJsonObject Portal::to_json(std::string doc_checksum) const
{
QJsonObject res;
res["src_offset_y"] = src_offset_y;
res["dst_checksum"] = QString::fromStdString(dst.document_checksum);
res["dst_offset_x"] = dst.book_state.offset_x;
res["dst_offset_y"] = dst.book_state.offset_y;
res["dst_zoom_level"] = dst.book_state.zoom_level;
if (src_offset_x) {
res["src_offset_x"] = src_offset_x.value();
}
res["same"] = (doc_checksum == dst.document_checksum);
add_metadata_to_json(res);
return res;
}
void Portal::from_json(const QJsonObject& json_object)
{
src_offset_y = json_object["src_offset_y"].toDouble();
dst.document_checksum = json_object["dst_checksum"].toString().toStdString();
dst.book_state.offset_x = json_object["dst_offset_x"].toDouble();
dst.book_state.offset_y = json_object["dst_offset_y"].toDouble();
dst.book_state.zoom_level = json_object["dst_zoom_level"].toDouble();
if (json_object.contains("src_offset_x")) {
src_offset_x = json_object["src_offset_x"].toDouble();
}
load_metadata_from_json(json_object);
}
void Portal::add_to_tuples(std::vector>& tuples) {
tuples.push_back({ "src_offset_y", src_offset_y });
tuples.push_back({ "dst_document", QString::fromStdString(dst.document_checksum) });
tuples.push_back({ "dst_offset_x", dst.book_state.offset_x });
tuples.push_back({ "dst_offset_y", dst.book_state.offset_y });
tuples.push_back({ "dst_zoom_level", dst.book_state.zoom_level });
if (src_offset_x) {
tuples.push_back({ "src_offset_x", src_offset_x.value()});
}
}
bool operator==(const Mark& lhs, const Mark& rhs)
{
return (lhs.symbol == rhs.symbol) && (lhs.y_offset == rhs.y_offset);
}
bool operator==(const BookMark& lhs, const BookMark& rhs)
{
return (lhs.y_offset_ == rhs.y_offset_) && (lhs.description == rhs.description);
}
bool operator==(const fz_point& lhs, const fz_point& rhs) {
return (lhs.y == rhs.y) && (lhs.x == rhs.x);
}
bool operator==(const Highlight& lhs, const Highlight& rhs)
{
return (lhs.selection_begin.x == rhs.selection_begin.x) && (lhs.selection_end.x == rhs.selection_end.x) &&
(lhs.selection_begin.y == rhs.selection_begin.y) && (lhs.selection_end.y == rhs.selection_end.y);
}
bool operator==(const Portal& lhs, const Portal& rhs)
{
return (lhs.src_offset_y == rhs.src_offset_y) && (lhs.dst.document_checksum == rhs.dst.document_checksum);
}
bool are_same(const BookMark& lhs, const BookMark& rhs) {
return are_same(lhs.begin_x, rhs.begin_x) && are_same(lhs.begin_y, rhs.begin_y) && are_same(lhs.end_x, rhs.end_x) && are_same(lhs.end_y, rhs.end_y);
}
bool are_same(const Highlight& lhs, const Highlight& rhs) {
return are_same(lhs.selection_begin, rhs.selection_begin) && are_same(lhs.selection_end, rhs.selection_end);
}
bool Portal::is_visible() const {
return src_offset_x.has_value();
}
AbsoluteRect BookMark::get_rectangle() const{
if (end_y > -1) {
return AbsoluteRect(
AbsoluteDocumentPos{ begin_x, begin_y },
AbsoluteDocumentPos{ end_x, end_y }
);
}
else {
return AbsoluteRect(
AbsoluteDocumentPos{ begin_x - BOOKMARK_RECT_SIZE, begin_y - BOOKMARK_RECT_SIZE },
AbsoluteDocumentPos{ begin_x + BOOKMARK_RECT_SIZE, begin_y + BOOKMARK_RECT_SIZE }
);
}
}
AbsoluteRect Portal::get_rectangle() const{
return AbsoluteRect(
AbsoluteDocumentPos{ src_offset_x.value() - BOOKMARK_RECT_SIZE, src_offset_y - BOOKMARK_RECT_SIZE},
AbsoluteDocumentPos{ src_offset_x.value() + BOOKMARK_RECT_SIZE, src_offset_y + BOOKMARK_RECT_SIZE}
);
}
float BookMark::get_y_offset() const{
if (begin_y != -1) return begin_y;
return y_offset_;
}
AbsoluteDocumentPos BookMark::begin_pos() {
return AbsoluteDocumentPos{ begin_x, begin_y };
}
AbsoluteDocumentPos BookMark::end_pos() {
return AbsoluteDocumentPos{ end_x, end_y };
}
AbsoluteRect BookMark::rect() {
return AbsoluteRect(begin_pos(), end_pos());
}
AbsoluteRect FreehandDrawing::bbox(){
AbsoluteRect res;
if (points.size() > 0) {
res.x0 = points[0].pos.x;
res.x1 = points[0].pos.x;
res.y0 = points[0].pos.y;
res.y1 = points[0].pos.y;
for (int i = 1; i < points.size(); i++) {
res.x0 = std::min(points[i].pos.x, res.x0);
res.x1 = std::max(points[i].pos.x, res.x1);
res.y0 = std::min(points[i].pos.y, res.y0);
res.y1 = std::max(points[i].pos.y, res.y1);
}
}
return res;
}
void SearchResult::fill(Document* doc) {
if (rects.size() == 0) {
doc->fill_search_result(this);
}
}
std::string reference_type_string(ReferenceType rt) {
if (rt == ReferenceType::None) return "none";
if (rt == ReferenceType::Equation) return "equation";
if (rt == ReferenceType::Reference) return "reference";
if (rt == ReferenceType::Abbreviation) return "abbreviation";
if (rt == ReferenceType::Generic) return "generic";
if (rt == ReferenceType::Link) return "link";
return "";
}
================================================
FILE: pdf_viewer/book.h
================================================
#pragma once
#include
#include
#include
#include
//#include
#include
#include
#include
#include
#include
#include "coordinates.h"
class DocumentView;
class Document;
enum class SelectedObjectType {
Drawing,
Pixmap
};
struct SelectedObjectIndex {
int index;
SelectedObjectType type;
};
struct BookState {
std::wstring document_path;
float offset_y;
std::string uuid;
};
struct OverviewState {
float absolute_offset_y;
float absolute_offset_x = 0;
float zoom_level = -1;
Document* doc = nullptr;
std::optional overview_type;
};
struct OpenedBookState {
float zoom_level;
float offset_x;
float offset_y;
bool ruler_mode = false;
std::optional presentation_page = {};
std::optional ruler_rect = {};
float ruler_pos = 0;
int line_index = -1;
};
struct Annotation {
std::string creation_time;
std::string modification_time;
std::string uuid;
virtual QJsonObject to_json(std::string doc_checksum) const = 0;
virtual void add_to_tuples(std::vector>& tuples) = 0;
virtual std::vector> to_tuples();
virtual void from_json(const QJsonObject& json_object) = 0;
void add_metadata_to_json(QJsonObject& obj) const;
void load_metadata_from_json(const QJsonObject& obj);
QDateTime get_creation_datetime() const;
QDateTime get_modification_datetime() const;
void update_creation_time();
void update_modification_time();
};
/*
A mark is a location in the document labeled with a symbol (which is a single character [a-z]). For example
we can mark a location with symbol 'a' and later return to that location by going to the mark named 'a'.
Lower case marks are local to the document and upper case marks are global.
*/
struct Mark : Annotation {
float y_offset;
char symbol;
std::optional x_offset = {};
std::optional zoom_level = {};
QJsonObject to_json(std::string doc_checksum) const;
void from_json(const QJsonObject& json_object);
void add_to_tuples(std::vector>& tuples) override;
};
/*
A bookmark is similar to mark but instead of being indexed by a symbol, it has a description.
*/
struct BookMark : Annotation {
float y_offset_ = -1;
std::wstring description;
float begin_x = -1;
float begin_y = -1;
float end_x = -1;
float end_y = -1;
float color[3] = { 0 };
float font_size = -1;
std::wstring font_face;
AbsoluteDocumentPos begin_pos();
AbsoluteDocumentPos end_pos();
AbsoluteRect rect();
QJsonObject to_json(std::string doc_checksum) const;
void from_json(const QJsonObject& json_object);
void add_to_tuples(std::vector>& tuples) override;
float get_y_offset() const;
bool is_freetext() const;
bool is_box() const;
bool is_marked() const;
std::optional get_type() const;
AbsoluteRect get_rectangle() const;
};
struct Highlight : Annotation {
AbsoluteDocumentPos selection_begin;
AbsoluteDocumentPos selection_end;
std::wstring description;
std::wstring text_annot;
char type;
std::vector highlight_rects;
QJsonObject to_json(std::string doc_checksum) const;
void add_to_tuples(std::vector>& tuples) override;
void from_json(const QJsonObject& json_object);
};
struct PdfLink {
//fz_rect rect;
std::vector rects;
int source_page;
std::string uri;
};
enum OverviewSide {
bottom = 0,
top = 1,
left = 2,
right = 3
};
struct ParsedUri {
int page;
float x;
float y;
};
struct FreehandDrawingPoint {
AbsoluteDocumentPos pos;
float thickness;
};
enum class SearchCaseSensitivity {
CaseSensitive,
CaseInsensitive,
SmartCase
};
struct DocumentCharacter {
int c;
PagelessDocumentRect rect;
bool is_final = false;
fz_stext_block* stext_block;
fz_stext_line* stext_line;
fz_stext_char* stext_char;
};
struct FreehandDrawing {
std::vector points;
char type;
float alpha = 1;
QDateTime creattion_time;
AbsoluteRect bbox();
};
struct PixmapDrawing {
QPixmap pixmap;
AbsoluteRect rect;
};
struct CharacterAddress {
int page;
fz_stext_block* block;
fz_stext_line* line;
fz_stext_char* character;
Document* doc;
CharacterAddress* previous_character = nullptr;
bool advance(char c);
bool backspace();
bool next_char();
bool next_line();
bool next_block();
bool next_page();
float focus_offset();
};
struct DocumentViewState {
std::wstring document_path;
OpenedBookState book_state;
};
struct PortalViewState {
std::string document_checksum;
OpenedBookState book_state;
};
struct OverviewResizeData {
fz_rect original_rect;
NormalizedWindowPos original_normal_mouse_pos;
OverviewSide side_index;
};
struct OverviewMoveData {
fvec2 original_offsets;
NormalizedWindowPos original_normal_mouse_pos;
};
struct OverviewTouchMoveData {
AbsoluteDocumentPos overview_original_pos_absolute;
NormalizedWindowPos original_mouse_normalized_pos;
};
/*
A link is a connection between two document locations. For example when reading a paragraph that is referencing a figure,
we may want to link that paragraphs's location to the figure. We can then easily switch between the paragraph and the figure.
Also if helper window is opened, it automatically displays the closest link to the current location.
Note that this is different from PdfLink which is the built-in link functionality in PDF file format.
*/
struct Portal : Annotation {
static Portal with_src_offset(float src_offset);
PortalViewState dst;
float src_offset_y;
std::optional src_offset_x = {};
bool is_visible() const;
QJsonObject to_json(std::string doc_checksum) const;
void from_json(const QJsonObject& json_object);
void add_to_tuples(std::vector>& tuples) override;
AbsoluteRect get_rectangle() const;
};
bool operator==(const DocumentViewState& lhs, const DocumentViewState& rhs);
struct SearchResult {
std::vector rects;
int page;
int begin_index_in_page;
int end_index_in_page;
void fill(Document* doc);
};
struct TocNode {
std::vector children;
std::wstring title;
int page;
float y;
float x;
};
class Document;
struct CachedPageData {
Document* doc = nullptr;
int page;
float zoom_level;
};
enum class ReferenceType {
Generic,
Equation,
Reference,
Abbreviation,
Link,
None
};
std::string reference_type_string(ReferenceType rt);
struct SmartViewCandidate {
Document* doc = nullptr;
AbsoluteRect source_rect;
std::wstring source_text;
std::variant target_pos;
ReferenceType reference_type = ReferenceType::None;
Document* get_document(DocumentView* view);
DocumentPos get_docpos(DocumentView* view);
AbsoluteDocumentPos get_abspos(DocumentView* view);
};
/*
A cached page consists of cached_page_data which is the header that describes the rendered location
and the actual rendered page. We have two different rendered formats: the pixmap we got from mupdf and
the cached_page_texture which is an OpenGL texture. The reason we need both formats in the structure is because
we render the pixmaps in a background mupdf thread, but in order to be able to use a texture in the main thread,
the texture has to be created in the main thread. Therefore, we fill this structure's cached_page_pixmap value in the
background thread and then send it to the main thread where we create the texture (which is a relatively fast operation
so it doesn't matter that it is in the main thread). When cached_page_texture is created, we can safely delete the
cached_page_pixmap, but the pixmap can only be deleted in the thread that it was created in, so we have to once again,
send the cached_page_texture back to the background thread to be deleted.
*/
struct CachedPage {
CachedPageData cached_page_data;
fz_pixmap* cached_page_pixmap = nullptr;
// last_access_time is used to garbage collect old pages
unsigned int last_access_time;
GLuint cached_page_texture;
};
bool operator==(const CachedPageData& lhs, const CachedPageData& rhs);
/*
When a document does not have built-in links to the figures, we use a heuristic to find the figures
and index them in FigureData structure. Using this, we can quickly find the figures when user clicks on the
text descripbing the figure (for example 'Fig. 2.13')
*/
struct IndexedData {
int page;
float y_offset;
std::wstring text;
};
bool operator==(const Mark& lhs, const Mark& rhs);
bool operator==(const BookMark& lhs, const BookMark& rhs);
bool operator==(const Highlight& lhs, const Highlight& rhs);
bool operator==(const Portal& lhs, const Portal& rhs);
bool are_same(const BookMark& lhs, const BookMark& rhs);
bool are_same(const Highlight& lhs, const Highlight& rhs);
================================================
FILE: pdf_viewer/checksum.cpp
================================================
#include
#include "checksum.h"
std::string compute_checksum(const QString& file_name, QCryptographicHash::Algorithm hash_algorithm)
{
QFile infile(file_name);
qint64 file_size = infile.size();
const qint64 buffer_size = 10240;
if (infile.open(QIODevice::ReadOnly))
{
char buffer[buffer_size];
int bytes_read;
int read_size = qMin(file_size, buffer_size);
QCryptographicHash hash(hash_algorithm);
while (read_size > 0 && (bytes_read = infile.read(buffer, read_size)) > 0)
{
file_size -= bytes_read;
hash.addData(buffer, bytes_read);
read_size = qMin(file_size, buffer_size);
}
infile.close();
return QString(hash.result().toHex()).toStdString();
}
return "";
}
CachedChecksummer::CachedChecksummer(const std::vector>* loaded_checksums) {
if (loaded_checksums) {
for (const auto& [path, checksum_] : *loaded_checksums) {
std::string checksum = QString::fromStdWString(checksum_).toStdString();
cached_checksums[path] = checksum;
cached_paths[checksum].push_back(path);
}
}
}
std::optional CachedChecksummer::get_checksum_fast(std::wstring file_path) {
// return the checksum only if it is alreay precomputed in cache
if (cached_checksums.find(file_path) != cached_checksums.end()) {
return cached_checksums[file_path];
}
return {};
}
std::string CachedChecksummer::get_checksum(std::wstring file_path) {
auto cached_checksum = get_checksum_fast(file_path);
if (!cached_checksum) {
std::string checksum = compute_checksum(QString::fromStdWString(file_path), QCryptographicHash::Md5);
cached_checksums[file_path] = checksum;
cached_paths[checksum].push_back(file_path);
}
return cached_checksums[file_path];
}
std::optional CachedChecksummer::get_path(std::string checksum) {
const std::vector paths = cached_paths[checksum];
for (const auto& path_string : paths) {
if (QFile::exists(QString::fromStdWString(path_string))) {
return path_string;
}
}
return {};
}
int CachedChecksummer::num_docs_with_checksum(std::string checksum) {
if (cached_paths.find(checksum) != cached_paths.end()) {
return cached_paths[checksum].size();
}
return 0;
}
================================================
FILE: pdf_viewer/checksum.h
================================================
#pragma once
#include
#include
#include
#include
#include
#include
#include
std::string compute_checksum(const QString& file_name, QCryptographicHash::Algorithm hash_algorithm);
class CachedChecksummer {
private:
std::unordered_map cached_checksums;
std::unordered_map> cached_paths;
public:
CachedChecksummer(const std::vector>* loaded_checksums);
std::string get_checksum(std::wstring file_path);
std::optional get_checksum_fast(std::wstring file_path);
std::optional get_path(std::string checksum);
int num_docs_with_checksum(std::string checksum);
};
================================================
FILE: pdf_viewer/config.cpp
================================================
#include "config.h"
#include "utils.h"
#include
#include