#include <cmath>
#include "document_view.h"
#include "checksum.h"
#include "database.h"
#include "document.h"
#include "qlogging.h"
#include "utils.h"
#include "config.h"
#include "ui.h"
extern float MOVE_SCREEN_PERCENTAGE;
extern float FIT_TO_PAGE_WIDTH_RATIO;
extern float RULER_PADDING;
extern float RULER_X_PADDING;
extern bool EXACT_HIGHLIGHT_SELECT;
extern bool VERBOSE;
extern float PAGE_SPACE_X;
extern float PAGE_SPACE_Y;
extern bool REAL_PAGE_SEPARATION;
extern float OVERVIEW_SIZE[2];
extern float OVERVIEW_OFFSET[2];
extern bool SHOULD_HIGHLIGHT_LINKS;
extern float HIDE_SYNCTEX_HIGHLIGHT_TIMEOUT;
extern int PAGE_PADDINGS;
extern bool SAME_WIDTH;
extern bool SCROLL_PAST_DOCUMENT_ENDS;
extern bool HORIZONTAL_SCROLL_PAST_PAGE_ENDS;
extern bool RECTO_VERSO_ADJUSTMENT;
DocumentView::DocumentView(DatabaseManager* db_manager,
DocumentManager* document_manager,
CachedChecksummer* checksummer) :
db_manager(db_manager),
document_manager(document_manager),
checksummer(checksummer)
{
page_space_x = PAGE_SPACE_X;
page_space_y = PAGE_SPACE_Y;
}
DocumentView::~DocumentView() {
}
float DocumentView::get_zoom_level() {
return zoom_level;
}
DocumentViewState DocumentView::get_state() {
DocumentViewState res;
if (current_document) {
res.document_path = current_document->get_path();
res.book_state.offset_x = get_offset_x();
res.book_state.offset_y = get_offset_y();
res.book_state.zoom_level = get_zoom_level();
res.book_state.ruler_mode = is_ruler_mode_;
res.book_state.ruler_pos = ruler_pos;
res.book_state.ruler_rect = ruler_rect;
res.book_state.line_index = line_index;
res.book_state.presentation_page = presentation_page_number;
}
return res;
}
PortalViewState DocumentView::get_checksum_state() {
PortalViewState res;
if (current_document) {
res.document_checksum = current_document->get_checksum();
res.book_state.offset_x = get_offset_x();
res.book_state.offset_y = get_offset_y();
res.book_state.zoom_level = get_zoom_level();
}
return res;
}
void DocumentView::handle_escape() {
}
void DocumentView::exit_ruler_mode() {
is_ruler_mode_ = false;
}
void DocumentView::set_book_state(OpenedBookState state) {
set_offsets(state.offset_x, state.offset_y);
set_zoom_level(state.zoom_level, true);
presentation_page_number = state.presentation_page;
is_ruler_mode_ = state.ruler_mode;
ruler_pos = state.ruler_pos;
ruler_rect = state.ruler_rect;
line_index = state.line_index;
}
bool DocumentView::set_pos(AbsoluteDocumentPos pos) {
return set_offsets(pos.x, pos.y);
}
void DocumentView::set_virtual_pos(VirtualPos pos, bool force) {
if (!fast_coordinates()) {
offset = pos;
}
else {
set_offsets(pos.x, pos.y, force);
}
}
bool DocumentView::set_offsets(float new_offset_x, float new_offset_y, bool force) {
bool truncated = false;
if (current_document == nullptr) return false;
int num_pages = current_document->num_pages();
if (num_pages == 0) return false;
float halfscreen_offset = !SCROLL_PAST_DOCUMENT_ENDS ? view_height / 2 / zoom_level : 0;
float max_y_offset = current_document->get_accum_page_height(num_pages - 1) + current_document->get_page_height(num_pages - 1) - halfscreen_offset + get_status_bar_height();
float min_y_offset = halfscreen_offset;
float min_x_offset_normal = get_min_valid_x(false);
float max_x_offset_normal = get_max_valid_x(false);
float min_x_offset_relenting = get_min_valid_x(true);
float max_x_offset_relenting = get_max_valid_x(true);
float max_x_offset = is_relenting ? max_x_offset_relenting : max_x_offset_normal;
float min_x_offset = is_relenting ? min_x_offset_relenting : min_x_offset_normal;
float relent_threshold = view_width / 4 / zoom_level;
if (TOUCH_MODE) {
if ((new_offset_x - max_x_offset_normal > relent_threshold) || (min_x_offset_normal - new_offset_x > relent_threshold)) {
is_relenting = true;
}
else {
is_relenting = false;
}
}
if (!force) {
if (new_offset_y > max_y_offset) { new_offset_y = max_y_offset; truncated = true; }
if (new_offset_y < min_y_offset) { new_offset_y = min_y_offset; truncated = true; }
if (new_offset_x > max_x_offset) { new_offset_x = max_x_offset; truncated = true; }
if (new_offset_x < min_x_offset) { new_offset_x = min_x_offset; truncated = true; }
}
offset = absolute_to_virtual_pos(AbsoluteDocumentPos{new_offset_x, new_offset_y});
return truncated;
}
Document* DocumentView::get_document() {
return current_document;
}
std::optional<Portal> DocumentView::find_closest_portal(bool limit) {
if (current_document) {
float offset_y = get_offsets().y;
auto res = current_document->find_closest_portal(offset_y);
if (res) {
if (!limit) {
return res;
}
else {
if (std::abs(res.value().src_offset_y - offset_y) < 500.0f) {
return res;
}
}
}
}
return {};
}
std::optional<BookMark> DocumentView::find_closest_bookmark() {
if (current_document) {
float offset_y = get_offsets().y;
int bookmark_index = current_document->find_closest_bookmark_index(current_document->get_bookmarks(), offset_y);
const std::vector<BookMark>& bookmarks = current_document->get_bookmarks();
if ((bookmark_index >= 0) && (bookmark_index < bookmarks.size())) {
if (std::abs(bookmarks[bookmark_index].get_y_offset() - offset_y) < 1000.0f) {
return bookmarks[bookmark_index];
}
}
}
return {};
}
void DocumentView::goto_portal(Portal* link) {
if (link) {
if (get_document() &&
get_document()->get_checksum() == link->dst.document_checksum) {
set_book_state(link->dst.book_state);
}
else {
auto destination_path = checksummer->get_path(link->dst.document_checksum);
if (destination_path) {
open_document(destination_path.value(), nullptr);
set_book_state(link->dst.book_state);
}
}
}
}
void DocumentView::delete_closest_portal() {
if (current_document) {
current_document->delete_closest_portal(get_offsets().y);
}
}
void DocumentView::delete_closest_bookmark() {
if (current_document) {
delete_closest_bookmark_to_offset(get_offsets().y);
}
}
Highlight DocumentView::get_highlight_with_index(int index) {
return current_document->get_highlights()[index];
}
void DocumentView::delete_highlight_with_index(int index) {
current_document->delete_highlight_with_index(index);
}
void DocumentView::delete_bookmark_with_index(int index) {
current_document->delete_bookmark_with_index(index);
}
void DocumentView::delete_highlight(Highlight hl) {
current_document->delete_highlight(hl);
}
void DocumentView::delete_closest_bookmark_to_offset(float offset) {
current_document->delete_closest_bookmark(offset);
}
float DocumentView::get_offset_x() {
return virtual_to_absolute_pos(offset).x;
}
float DocumentView::get_offset_y() {
return virtual_to_absolute_pos(offset).y;
}
AbsoluteDocumentPos DocumentView::get_offsets() {
return virtual_to_absolute_pos(offset);
}
VirtualPos DocumentView::get_virtual_offset() {
return offset;
}
int DocumentView::get_view_height() {
return view_height;
}
int DocumentView::get_view_width() {
return view_width;
}
void DocumentView::set_null_document() {
current_document = nullptr;
was_set_to_null = true;
}
void DocumentView::set_offset_x(float new_offset_x) {
set_offsets(new_offset_x, get_offset_y());
}
void DocumentView::set_offset_y(float new_offset_y) {
if (is_two_page_mode()) {
int num_pages = current_document->num_pages();
float halfscreen_offset = !SCROLL_PAST_DOCUMENT_ENDS ? view_height / 2 / zoom_level : 0;
int actual_num_pages = (num_pages + 1) / 2;
float max_y_offset = max_virtual_y - halfscreen_offset;
AbsoluteDocumentPos current = get_offsets();
current.y = new_offset_y;
VirtualPos new_pos = absolute_to_virtual_pos(current);
offset.y = new_pos.y;
offset.y = std::max(halfscreen_offset, offset.y);
offset.y = std::min(offset.y, max_y_offset);
}
else {
set_offsets(get_offset_x(), new_offset_y);
}
}
std::optional<PdfLink> DocumentView::get_link_in_pos(WindowPos pos) {
if (!current_document) return {};
DocumentPos doc_pos = window_to_document_pos(pos);
return current_document->get_link_in_pos(doc_pos);
}
int DocumentView::get_highlight_index_in_pos(WindowPos window_pos) {
AbsoluteDocumentPos pos = window_pos.to_absolute(this);
int smallest_containing_highlight_index = -1;
int min_length = INT_MAX;
if (current_document->can_use_highlights()) {
const std::vector<Highlight>& highlights = current_document->get_highlights();
for (size_t i = 0; i < highlights.size(); i++) {
for (size_t j = 0; j < highlights[i].highlight_rects.size(); j++) {
if (highlights[i].highlight_rects[j].contains(pos)) {
int length = highlights[i].description.size();
if (length < min_length) {
min_length = length;
smallest_containing_highlight_index = i;
}
}
}
}
}
return smallest_containing_highlight_index;
}
void DocumentView::add_mark(char symbol) {
if (current_document) {
AbsoluteDocumentPos current_offset = get_offsets();
current_document->add_mark(symbol, current_offset.y, current_offset.x, zoom_level);
}
}
std::string DocumentView::add_bookmark(std::wstring desc) {
if (current_document) {
return current_document->add_bookmark(desc, get_offset_y());
}
return "";
}
std::string DocumentView::add_highlight(AbsoluteDocumentPos selection_begin, AbsoluteDocumentPos selection_end, char type) {
if (current_document) {
std::deque<AbsoluteRect> selected_characters;
std::vector<AbsoluteRect> merged_characters;
std::wstring selected_text;
get_text_selection(selection_begin, selection_end, !EXACT_HIGHLIGHT_SELECT, selected_characters, selected_text);
merge_selected_character_rects(selected_characters, merged_characters);
if (selected_text.size() > 0) {
return current_document->add_highlight(selected_text, (std::vector<AbsoluteRect>&)merged_characters, selection_begin, selection_end, type);
}
}
return "";
}
void DocumentView::on_view_size_change(int new_width, int new_height) {
view_width = new_width;
view_height = new_height;
}
NormalizedWindowPos DocumentView::absolute_to_window_pos(AbsoluteDocumentPos abs) {
VirtualPos vpos = absolute_to_virtual_pos(abs);
WindowPos window_pos = virtual_to_window_pos(vpos);
return window_pos.to_window_normalized(this);
}
NormalizedWindowRect DocumentView::absolute_to_window_rect(AbsoluteRect doc_rect) {
NormalizedWindowPos top_left = doc_rect.top_left().to_window_normalized(this);
NormalizedWindowPos bottom_right = doc_rect.bottom_right().to_window_normalized(this);
return NormalizedWindowRect(top_left, bottom_right);
}
NormalizedWindowPos DocumentView::document_to_window_pos(DocumentPos doc_pos) {
if (current_document) {
WindowPos window_pos = document_to_window_pos_in_pixels_uncentered(doc_pos);
double halfwidth = static_cast<double>(view_width) / 2;
double halfheight = static_cast<double>(view_height) / 2;
float window_x = static_cast<float>((window_pos.x - halfwidth) / halfwidth);
float window_y = static_cast<float>((window_pos.y - halfheight) / halfheight);
return { window_x, -window_y };
}
return { -10, -10 };
}
WindowPos DocumentView::absolute_to_window_pos_in_pixels(AbsoluteDocumentPos abspos) {
VirtualPos vpos = absolute_to_virtual_pos(abspos);
WindowPos window_pos;
window_pos.y = (vpos.y - offset.y) * zoom_level + view_height / 2;
window_pos.x = (vpos.x + offset.x) * zoom_level + view_width / 2;
return window_pos;
}
WindowPos DocumentView::document_to_window_pos_in_pixels_uncentered(DocumentPos doc_pos) {
VirtualPos vpos = document_to_virtual_pos(doc_pos);
return virtual_to_window_pos(vpos);
}
WindowPos DocumentView::document_to_window_pos_in_pixels_banded(DocumentPos doc_pos) {
if (!fast_coordinates()) {
VirtualPos vpos = document_to_virtual_pos(doc_pos);
WindowPos window_pos = virtual_to_window_pos(vpos);
return window_pos;
}
else {
AbsoluteDocumentPos abspos = current_document->document_to_absolute_pos(doc_pos);
WindowPos window_pos;
window_pos.y = static_cast<int>(std::roundf((abspos.y - offset.y) * zoom_level + static_cast<float>(view_height) / 2.0f));
window_pos.x = static_cast<int>(std::roundf((abspos.x + offset.x) * zoom_level + static_cast<float>(view_width) / 2.0f));
return window_pos;
}
}
WindowRect DocumentView::document_to_window_irect(DocumentRect doc_rect) {
WindowPos top_left = doc_rect.top_left().to_window(this);
WindowPos bottom_right = doc_rect.bottom_right().to_window(this);
return WindowRect(top_left, bottom_right);
}
NormalizedWindowRect DocumentView::document_to_window_rect(DocumentRect doc_rect) {
NormalizedWindowPos top_left = doc_rect.top_left().to_window_normalized(this);
NormalizedWindowPos bottom_right = doc_rect.bottom_right().to_window_normalized(this);
return NormalizedWindowRect(top_left, bottom_right);
}
NormalizedWindowRect DocumentView::document_to_window_rect_pixel_perfect(DocumentRect doc_rect, int pixel_width, int pixel_height, bool banded) {
if ((pixel_width <= 0) || (pixel_height <= 0)) {
return doc_rect.to_window_normalized(this);
}
WindowPos top_left, bottom_right;
if (banded) {
top_left = document_to_window_pos_in_pixels_banded(doc_rect.top_left());
bottom_right = document_to_window_pos_in_pixels_banded(doc_rect.bottom_right());
}
else {
top_left = document_to_window_pos_in_pixels_uncentered(doc_rect.top_left());
bottom_right = document_to_window_pos_in_pixels_uncentered(doc_rect.bottom_right());
}
bottom_right.x -= ((bottom_right.x - top_left.x) - pixel_width);
if (!banded) {
bottom_right.y -= ((bottom_right.y - top_left.y) - pixel_height);
}
NormalizedWindowPos top_left_normalized = top_left.to_window_normalized(this);
NormalizedWindowPos bottom_right_normalized = bottom_right.to_window_normalized(this);
return NormalizedWindowRect(top_left_normalized, bottom_right_normalized);
}
DocumentPos DocumentView::window_to_document_pos(WindowPos window_pos) {
if (current_document) {
return window_to_absolute_document_pos(window_pos).to_document(current_document);
}
else {
return { -1, 0, 0 };
}
}
AbsoluteDocumentPos DocumentView::window_to_absolute_document_pos(WindowPos window_pos) {
VirtualPos virtual_pos = window_to_virtual_pos(window_pos);
return virtual_to_absolute_pos(virtual_pos);
}
NormalizedWindowPos DocumentView::window_to_normalized_window_pos(WindowPos window_pos) {
float normal_x = 2 * (static_cast<float>(window_pos.x) - view_width / 2.0f) / view_width;
float normal_y = -2 * (static_cast<float>(window_pos.y) - view_height / 2.0f) / view_height;
return { normal_x, normal_y };
}
void DocumentView::goto_mark(char symbol) {
if (current_document) {
float new_y_offset = 0.0f;
std::optional<Mark> mark = current_document->get_mark_if_exists(symbol);
if (mark) {
set_offset_y(mark->y_offset);
if (mark->x_offset) {
set_offset_x(mark->x_offset.value());
set_zoom_level(mark->zoom_level.value(), true);
}
}
}
}
void DocumentView::goto_end() {
if (current_document) {
int last_page_index = current_document->num_pages() - 1;
set_offset_y(current_document->get_accum_page_height(last_page_index) + current_document->get_page_height(last_page_index));
}
}
void DocumentView::goto_left_smart() {
float left_ratio, right_ratio;
int normal_page_width;
float page_width = current_document->get_page_size_smart(true, get_center_page_number(), &left_ratio, &right_ratio, &normal_page_width);
float view_left_offset = (page_width / 2 - view_width / zoom_level / 2);
set_offset_x(view_left_offset);
}
void DocumentView::goto_left() {
float page_width = current_document->get_page_width(get_center_page_number());
float view_left_offset = (page_width / 2 - view_width / zoom_level / 2);
set_offset_x(view_left_offset);
}
void DocumentView::goto_right_smart() {
float left_ratio, right_ratio;
int normal_page_width;
float page_width = current_document->get_page_size_smart(true, get_center_page_number(), &left_ratio, &right_ratio, &normal_page_width);
float view_left_offset = -(page_width / 2 - view_width / zoom_level / 2);
set_offset_x(view_left_offset);
}
void DocumentView::goto_right() {
float page_width = current_document->get_page_width(get_center_page_number());
float view_left_offset = -(page_width / 2 - view_width / zoom_level / 2);
set_offset_x(view_left_offset);
}
float DocumentView::set_zoom_level(float zl, bool should_exit_auto_resize_mode, bool readjust) {
#ifdef SIOYEK_ANDROID
const float max_zoom_level = 6.0f;
#else
const float max_zoom_level = 10.0f;
#endif
if (TOUCH_MODE){
int page_number = get_center_page_number();
if (page_number >= 0){
float min_zoom_level = view_width / current_document->get_page_width(page_number);
if (zl < min_zoom_level){
zl = min_zoom_level;
}
}
}
if (zl > max_zoom_level) {
zl = max_zoom_level;
}
if (should_exit_auto_resize_mode) {
this->is_auto_resize_mode = false;
}
zoom_level = zl;
if (readjust) {
this->readjust_to_screen();
}
return zoom_level;
}
float DocumentView::zoom_in(float zoom_factor, bool readjust) {
#ifdef SIOYEK_ANDROID
const float max_zoom_level = 6.0f;
#else
const float max_zoom_level = 10.0f;
#endif
float new_zoom_level = zoom_level * zoom_factor;
if (new_zoom_level > max_zoom_level) {
new_zoom_level = max_zoom_level;
}
return set_zoom_level(new_zoom_level, true, readjust);
}
float DocumentView::zoom_out(float zoom_factor, bool readjust) {
return set_zoom_level(zoom_level / zoom_factor, true, readjust);
}
float DocumentView::zoom_in_cursor(WindowPos mouse_pos, float zoom_factor) {
VirtualPos prev_mouse_abs_pos = window_to_virtual_pos(mouse_pos);
VirtualPos prev_document_offset = get_virtual_offset();
prev_document_offset.x = -prev_document_offset.x;
fvec2 diff = prev_mouse_abs_pos - prev_document_offset;
float old_zoom_level = zoom_level;
float new_zoom_level = set_zoom_level(zoom_level * zoom_factor, false);
float actual_inc = new_zoom_level / old_zoom_level;
fvec2 new_diff = diff / actual_inc;
VirtualPos new_pos = prev_mouse_abs_pos - new_diff;
new_pos.x = -new_pos.x;
set_virtual_pos(new_pos, true);
return new_zoom_level;
}
float DocumentView::zoom_out_cursor(WindowPos mouse_pos, float zoom_factor) {
return zoom_in_cursor(mouse_pos, 1.0f / zoom_factor);
}
bool DocumentView::move_absolute(float dx, float dy, bool force) {
AbsoluteDocumentPos prev_offsets = get_offsets();
return set_offsets(prev_offsets.x + dx, prev_offsets.y + dy, force);
}
bool DocumentView::move_virtual(float dx, float dy, bool force) {
offset.x += dx;
offset.y += dy;
if (cached_virtual_rects.size() > 0 && !needs_refill) {
float halfwidth = view_width / 2 / zoom_level;
float halfheight = !SCROLL_PAST_DOCUMENT_ENDS ? view_height / 2 / zoom_level : 0;
if (min_virtual_x + halfwidth > 0 && max_virtual_x - halfwidth < 0) {
offset.x = 0;
}
else {
offset.x = std::max(min_virtual_x + halfwidth, offset.x);
offset.x = std::min(max_virtual_x - halfwidth, offset.x);
}
offset.y = std::min(max_virtual_y - halfheight, offset.y);
offset.y = std::max(halfheight, offset.y);
}
return false;
}
bool DocumentView::move(float dx, float dy, bool force) {
float abs_dx = (dx / zoom_level);
float abs_dy = (dy / zoom_level);
if (!fast_coordinates()) {
return move_virtual(abs_dx, abs_dy, force);
}
else {
return move_absolute(abs_dx, abs_dy, force);
}
}
void DocumentView::get_absolute_delta_from_doc_delta(float dx, float dy, float* abs_dx, float* abs_dy) {
*abs_dx = (dx / zoom_level);
*abs_dy = (dy / zoom_level);
}
int DocumentView::get_center_page_number() {
if (current_document) {
return current_document->get_offset_page_number(get_offset_y());
}
else {
return -1;
}
}
void DocumentView::get_visible_pages(int window_height, std::vector<int>& visible_pages) {
if (!current_document) return;
AbsoluteDocumentPos abs_offset = virtual_to_absolute_pos(offset);
float window_y_range_begin = abs_offset.y - window_height / (1.5 * zoom_level);
float window_y_range_end = abs_offset.y + window_height / (1.5 * zoom_level);
window_y_range_begin -= 1;
window_y_range_end += 1;
if (!fast_coordinates()) {
fill_cached_virtual_rects();
for (int i = 0; i < cached_virtual_rects.size(); i++){
if (virtual_to_normalized_window_rect(cached_virtual_rects[i]).is_visible(0.5f)) {
visible_pages.push_back(i);
}
}
}
else {
current_document->get_visible_pages(window_y_range_begin, window_y_range_end, visible_pages);
}
}
void DocumentView::move_pages(int num_pages) {
if (!current_document) return;
int current_page = get_center_page_number();
if (current_page == -1) {
current_page = 0;
}
int padding = two_page_mode ? page_space_y : PAGE_PADDINGS;
move_virtual(0, num_pages * (current_document->get_page_height(current_page) + padding));
}
void DocumentView::move_screens(int num_screens) {
float screen_height_in_doc_space = view_height / zoom_level;
set_offset_y(get_offset_y() + num_screens * screen_height_in_doc_space * MOVE_SCREEN_PERCENTAGE);
}
void DocumentView::reset_doc_state() {
zoom_level = 1.0f;
set_offsets(0.0f, 0.0f);
is_ruler_mode_ = false;
presentation_page_number = {};
cached_virtual_rects.clear();
same_width_mode_first_page_width = {};
}
void DocumentView::open_document(const std::wstring& doc_path,
bool* invalid_flag,
bool load_prev_state,
std::optional<OpenedBookState> prev_state,
bool force_load_dimensions) {
last_opened_file_path = doc_path;
was_set_to_null = false;
std::wstring canonical_path = get_canonical_path(doc_path);
if (canonical_path == L"") {
current_document = nullptr;
return;
}
current_document = document_manager->get_document(canonical_path);
if (!current_document->open(invalid_flag, force_load_dimensions)) {
current_document = nullptr;
}
reset_doc_state();
if (prev_state) {
zoom_level = prev_state.value().zoom_level;
AbsoluteDocumentPos prev_offset;
prev_offset.x = prev_state.value().offset_x;
prev_offset.y = prev_state.value().offset_y;
set_offsets(prev_offset.x, prev_offset.y);
is_auto_resize_mode = false;
}
else if (load_prev_state) {
std::optional<std::string> checksum = checksummer->get_checksum_fast(canonical_path);
std::vector<OpenedBookState> prev_state;
if (checksum && db_manager->select_opened_book(checksum.value(), prev_state)) {
if (prev_state.size() > 1) {
LOG(std::cerr << "more than one file with one path, this should not happen!" << std::endl);
}
}
if (prev_state.size() > 0) {
OpenedBookState previous_state = prev_state[0];
zoom_level = previous_state.zoom_level;
set_offsets(previous_state.offset_x, previous_state.offset_y);
is_auto_resize_mode = false;
}
else {
if (current_document) {
fit_to_page_width();
is_auto_resize_mode = true;
set_offset_y(view_height / 2 / zoom_level);
}
}
}
}
float DocumentView::get_page_offset(int page) {
if (!current_document) return 0.0f;
int max_page = current_document->num_pages() - 1;
if (page > max_page) {
page = max_page;
}
return current_document->get_accum_page_height(page);
}
void DocumentView::goto_offset_within_page(int page, float offset_y) {
AbsoluteDocumentPos prev_offset = get_offsets();
set_offsets(prev_offset.x, get_page_offset(page) + offset_y);
}
void DocumentView::goto_page(int page) {
set_offset_y(get_page_offset(page) + view_height_in_document_space() / 2);
}
void DocumentView::fit_to_page_height_and_width_smart() {
int cp = get_center_page_number();
if (cp == -1) return;
float top_ratio, bottom_ratio;
int normal_page_height;
float left_ratio, right_ratio;
int normal_page_width;
int page_height = current_document->get_page_size_smart(false, cp, &top_ratio, &bottom_ratio, &normal_page_height);
int page_width = current_document->get_page_size_smart(true, cp, &left_ratio, &right_ratio, &normal_page_width);
float bottom_leftover = 1.0f - bottom_ratio;
float right_leftover = 1.0f - right_ratio;
float height_imbalance = top_ratio - bottom_leftover;
float width_imbalance = left_ratio - right_leftover;
float height_zoom_level = static_cast<float>(view_height - 20) / page_height;
float width_zoom_level = static_cast<float>(view_width - 20) / page_width;
float best_zoom_level = 1.0f;
set_zoom_level(qMin(width_zoom_level, height_zoom_level), true);
goto_offset_within_page(cp, (height_imbalance / 2.0f + 0.5f) * normal_page_height);
set_offset_x(-width_imbalance * normal_page_width / 2.0f);
}
void DocumentView::fit_to_page_height(bool smart) {
int cp = get_center_page_number();
if (cp == -1) return;
float top_ratio, bottom_ratio;
int normal_page_height;
int page_height = current_document->get_page_size_smart(false, cp, &top_ratio, &bottom_ratio, &normal_page_height);
float bottom_leftover = 1.0f - bottom_ratio;
float imbalance = top_ratio - bottom_leftover;
if (!smart) {
page_height = current_document->get_page_height(cp);
imbalance = 0;
}
set_zoom_level(static_cast<float>(view_height - 20) / page_height, true);
goto_offset_within_page(cp, (imbalance / 2.0f + 0.5f) * normal_page_height);
}
void DocumentView::fit_to_page_width(bool smart, bool ratio) {
if (!current_document) return;
int cp = get_center_page_number();
if (cp == -1) return;
if (smart) {
if (two_page_mode) {
int num_pages = current_document->num_pages();
int other_page = cp;
if (cp % 2 == 0) {
if (cp + 1 < num_pages) {
other_page = cp + 1;
}
}
else {
other_page = cp - 1;
}
float left_left_ratio, left_right_ratio;
int left_normal_page_width;
float left_page_width = current_document->get_page_size_smart(true, cp, &left_left_ratio, &left_right_ratio, &left_normal_page_width);
float right_left_ratio, right_right_ratio;
int right_normal_page_width;
float right_page_width = current_document->get_page_size_smart(true, other_page, &right_left_ratio, &right_right_ratio, &right_normal_page_width);
float right_leftover = 1.0f - right_right_ratio;
float imbalance = left_left_ratio - right_leftover;
left_page_width += static_cast<int>((1-left_right_ratio) * left_normal_page_width);
right_page_width += static_cast<int>(right_left_ratio * right_normal_page_width);
page_space_x = -(left_normal_page_width * (1 - left_right_ratio) + right_normal_page_width * right_left_ratio) / 2;
cached_virtual_rects.clear();
set_zoom_level(static_cast<float>(view_width) / (left_page_width + right_page_width + page_space_x * 2), false);
offset.x = -imbalance * (left_normal_page_width + right_normal_page_width + page_space_x * 2) / 4.0f;
}
else {
float left_ratio, right_ratio;
int normal_page_width;
int page_width = current_document->get_page_size_smart(true, cp, &left_ratio, &right_ratio, &normal_page_width);
float right_leftover = 1.0f - right_ratio;
float imbalance = left_ratio - right_leftover;
set_zoom_level(static_cast<float>(view_width) / page_width, false);
set_offset_x(-imbalance * normal_page_width / 2.0f);
}
}
else {
int page_width = current_document->get_page_width(cp);
if (SAME_WIDTH && same_width_mode_first_page_width.has_value()) {
page_width = same_width_mode_first_page_width.value();
}
int virtual_view_width = view_width;
if (ratio) {
virtual_view_width = static_cast<int>(static_cast<float>(view_width) * FIT_TO_PAGE_WIDTH_RATIO);
}
if (two_page_mode) {
page_space_x = PAGE_SPACE_X;
cached_virtual_rects.clear();
offset.x = 0;
page_width += page_width + page_space_x;
}
else {
set_offset_x(0);
}
set_zoom_level(static_cast<float>(virtual_view_width) / page_width, true);
}
}
void DocumentView::fit_to_page_height_width_minimum(int statusbar_height) {
int cp = get_center_page_number();
if (cp == -1) return;
int page_width = current_document->get_page_width(cp);
int page_height = current_document->get_page_height(cp);
float x_zoom_level = static_cast<float>(view_width) / page_width;
float y_zoom_level;
y_zoom_level = (static_cast<float>(view_height) - statusbar_height) / page_height;
set_offset_x(0);
set_zoom_level(std::min(x_zoom_level, y_zoom_level), true);
}
void DocumentView::persist(bool persist_drawings) {
if (!current_document) return;
AbsoluteDocumentPos abs_offset = get_offsets();
if (two_page_mode) {
abs_offset.x = 0;
}
db_manager->update_book(current_document->get_checksum(), zoom_level, abs_offset.x, abs_offset.y, current_document->detect_paper_name());
if (persist_drawings) {
current_document->persist_drawings();
current_document->persist_annotations();
current_document->persist_extras();
}
}
int DocumentView::get_current_chapter_index() {
const std::vector<int>& chapter_pages = current_document->get_flat_toc_pages();
if (chapter_pages.size() == 0) {
return -1;
}
int cp = get_center_page_number();
int current_chapter_index = 0;
int index = 0;
for (int p : chapter_pages) {
if (p <= cp) {
current_chapter_index = index;
}
index++;
}
return current_chapter_index;
}
std::wstring DocumentView::get_current_chapter_name() {
const std::vector<std::wstring>& chapter_names = current_document->get_flat_toc_names();
int current_chapter_index = get_current_chapter_index();
if (current_chapter_index > 0) {
return chapter_names[current_chapter_index];
}
return L"";
}
std::optional<std::pair<int, int>> DocumentView::get_current_page_range() {
int ci = get_current_chapter_index();
if (ci < 0) {
return {};
}
const std::vector<int>& chapter_pages = current_document->get_flat_toc_pages();
int range_begin = chapter_pages[ci];
int range_end = current_document->num_pages() - 1;
if ((size_t)ci < chapter_pages.size() - 1) {
range_end = chapter_pages[ci + 1];
}
return std::make_pair(range_begin, range_end);
}
void DocumentView::get_page_chapter_index(int page, std::vector<TocNode*> nodes, std::vector<int>& res) {
for (size_t i = 0; i < nodes.size(); i++) {
if ((i == nodes.size() - 1) && (nodes[i]->page <= page)) {
res.push_back(i);
get_page_chapter_index(page, nodes[i]->children, res);
return;
}
else {
if ((nodes[i]->page <= page) && (nodes[i + 1]->page > page)) {
res.push_back(i);
get_page_chapter_index(page, nodes[i]->children, res);
return;
}
}
}
}
std::vector<int> DocumentView::get_current_chapter_recursive_index() {
int curr_page = get_center_page_number();
std::vector<TocNode*> nodes = current_document->get_toc();
std::vector<int> res;
get_page_chapter_index(curr_page, nodes, res);
return res;
}
void DocumentView::goto_chapter(int diff) {
const std::vector<int>& chapter_pages = current_document->get_flat_toc_pages();
int curr_page = get_center_page_number();
int index = 0;
while ((size_t)index < chapter_pages.size() && chapter_pages[index] <= curr_page) {
index++;
}
if (index < chapter_pages.size() && chapter_pages[index] >= curr_page) {
index--;
}
int new_index = index + diff;
if (new_index < 0) {
goto_page(0);
}
else if ((size_t)new_index >= chapter_pages.size()) {
goto_end();
}
else {
goto_page(chapter_pages[new_index]);
}
}
float DocumentView::view_height_in_document_space() {
return static_cast<float>(view_height) / zoom_level;
}
void DocumentView::set_vertical_line_pos(float pos) {
ruler_pos = pos;
ruler_rect = {};
is_ruler_mode_ = true;
}
float DocumentView::get_ruler_pos() {
if (ruler_rect.has_value()) {
return ruler_rect->y1;
}
else {
return ruler_pos;
}
}
std::optional<AbsoluteRect> DocumentView::get_ruler_rect() {
return ruler_rect;
}
bool DocumentView::has_ruler_rect() {
return ruler_rect.has_value();
}
float DocumentView::get_ruler_window_y() {
float absol_end_y = get_ruler_pos();
absol_end_y += RULER_PADDING;
return absolute_to_window_pos({ 0.0f, absol_end_y }).y;
}
std::optional<NormalizedWindowRect> DocumentView::get_ruler_window_rect() {
if (has_ruler_rect()) {
AbsoluteRect absol_ruler_rect = get_ruler_rect().value();
absol_ruler_rect.y0 -= RULER_PADDING;
absol_ruler_rect.y1 += RULER_PADDING;
absol_ruler_rect.x0 -= RULER_X_PADDING;
absol_ruler_rect.x1 += RULER_X_PADDING;
return NormalizedWindowRect(absolute_to_window_rect(absol_ruler_rect));
}
return {};
}
void DocumentView::goto_vertical_line_pos() {
if (current_document) {
float new_y_offset = get_ruler_pos();
set_offset_y(new_y_offset);
is_ruler_mode_ = true;
}
}
void DocumentView::get_text_selection(AbsoluteDocumentPos selection_begin,
AbsoluteDocumentPos selection_end,
bool is_word_selection,
std::deque<AbsoluteRect>& selected_characters,
std::wstring& selected_text) {
if (current_document) {
current_document->get_text_selection(selection_begin, selection_end, is_word_selection, selected_characters, selected_text);
}
}
void DocumentView::get_line_selection(AbsoluteDocumentPos selection_begin, AbsoluteDocumentPos selection_end, std::deque<AbsoluteRect>& selected_characters, std::wstring& text_selection) {
if (current_document){
current_document->get_line_selection(selection_begin, selection_end, selected_characters, text_selection);
}
}
int DocumentView::get_page_offset() {
return current_document->get_page_offset();
}
void DocumentView::set_page_offset(int new_offset) {
current_document->set_page_offset(new_offset);
}
float DocumentView::get_max_valid_x(bool relenting) {
float page_width = current_document->get_page_width(get_center_page_number());
if (!relenting && !HORIZONTAL_SCROLL_PAST_PAGE_ENDS){
return std::abs(-view_width / zoom_level / 2 + page_width / 2);
}
else{
return std::abs(-view_width / zoom_level / 2 + 3 * page_width / 2);
}
}
float DocumentView::get_min_valid_x(bool relenting) {
float page_width = current_document->get_page_width(get_center_page_number());
if (!relenting && !HORIZONTAL_SCROLL_PAST_PAGE_ENDS){
return -std::abs(-view_width / zoom_level / 2 + page_width / 2);
}
else{
return -std::abs(-view_width / zoom_level / 2 + 3 * page_width / 2);
}
}
void DocumentView::rotate() {
int current_page = get_center_page_number();
current_document->rotate();
float new_offset = current_document->get_accum_page_height(current_page) + current_document->get_page_height(current_page) / 2;
set_offset_y(new_offset);
}
void DocumentView::goto_top_of_page() {
int current_page = get_center_page_number();
float offset_y = get_document()->get_accum_page_height(current_page) + static_cast<float>(view_height) / 2.0f / zoom_level;
set_offset_y(offset_y);
}
void DocumentView::goto_bottom_of_page() {
int current_page = get_center_page_number();
float offset_y = get_document()->get_accum_page_height(current_page + 1) - static_cast<float>(view_height) / 2.0f / zoom_level;
if (current_page + 1 == current_document->num_pages()) {
offset_y = get_document()->get_accum_page_height(current_page) + get_document()->get_page_height(current_page) - static_cast<float>(view_height) / 2.0f / zoom_level;
}
set_offset_y(offset_y);
}
int DocumentView::get_line_index() {
if (line_index == -1) {
return get_line_index_of_vertical_pos();
}
else {
return line_index;
}
}
void DocumentView::set_line_index(int index, int page) {
line_index = index;
is_ruler_mode_ = true;
if (page >= 0) {
auto lines = get_document()->get_page_lines(page);
if (index >= 0 && index < lines.size()) {
ruler_rect = lines[index];
}
}
}
int DocumentView::get_line_index_of_vertical_pos() {
DocumentPos line_doc_pos = current_document->absolute_to_page_pos_uncentered({ 0, get_ruler_pos() });
auto rects = current_document->get_page_lines(line_doc_pos.page);
int index = 0;
while ((size_t)index < rects.size() && rects[index].y0 < get_ruler_pos()) {
index++;
}
return index - 1;
}
int DocumentView::get_line_index_of_pos(DocumentPos line_doc_pos) {
AbsoluteDocumentPos line_abs_pos = line_doc_pos.to_absolute(current_document);
auto rects = current_document->get_page_lines(line_doc_pos.page, nullptr);
int page_width = current_document->get_page_width(line_doc_pos.page);
for (int i = 0; i < rects.size(); i++) {
if (rects[i].contains(line_abs_pos)) return i;
}
return -1;
}
int DocumentView::get_vertical_line_page() {
return current_document->absolute_to_page_pos({ 0, get_ruler_pos() }).page;
}
std::optional<std::wstring> DocumentView::get_selected_line_text() {
if (line_index >= 0) {
std::vector<std::wstring> lines;
std::vector<AbsoluteRect> line_rects = current_document->get_page_lines(get_vertical_line_page(), &lines);
if ((size_t)line_index < lines.size()) {
std::wstring content = lines[line_index];
return content;
}
else {
return {};
}
}
return {};
}
void DocumentView::get_rects_from_ranges(int page_number, const std::vector<PagelessDocumentRect>& line_char_rects, const std::vector<std::pair<int, int>>& ranges, std::vector<PagelessDocumentRect>& out_rects) {
for (int i = 0; i < ranges.size(); i++) {
auto [first, last] = ranges[i];
PagelessDocumentRect current_source_rect = get_range_rect_union(line_char_rects, first, last);
current_source_rect = current_document->document_to_absolute_rect(DocumentRect(current_source_rect, page_number));
out_rects.push_back(current_source_rect);
}
}
std::vector<SmartViewCandidate> DocumentView::find_line_definitions() {
std::vector<SmartViewCandidate> result;
if (line_index > 0) {
std::vector<std::wstring> lines;
std::vector<std::vector<PagelessDocumentRect>> line_char_rects;
int line_page_number = get_vertical_line_page();
std::vector<AbsoluteRect> line_rects = current_document->get_page_lines(line_page_number, &lines, &line_char_rects);
for (int i = 0; i < lines.size(); i++) {
assert(lines[i].size() == line_char_rects[i].size());
}
if ((size_t)line_index < lines.size()) {
std::wstring content = lines[line_index];
AbsoluteRect line_rect = line_rects[line_index];
float mid_y = (line_rect.y1 + line_rect.y0) / 2.0f;
line_rect.y0 = line_rect.y0 = mid_y;
std::vector<PdfLink> pdf_links = current_document->get_links_in_page_rect(get_vertical_line_page(), line_rect);
if (pdf_links.size() > 0) {
for (auto link : pdf_links) {
auto parsed_uri = parse_uri(get_document()->get_mupdf_context(), get_document()->doc, link.uri);
SmartViewCandidate candid;
candid.doc = get_document();
candid.source_rect = current_document->document_to_absolute_rect(DocumentRect(link.rects[0], line_page_number));
candid.source_text = get_document()->get_pdf_link_text(link);
candid.target_pos = DocumentPos{ parsed_uri.page - 1, parsed_uri.x, parsed_uri.y };
candid.reference_type = ReferenceType::Link;
result.push_back(candid);
}
return result;
}
std::wstring item_regex(L"[a-zA-Z]{2,}\\.?[ \t]+[0-9]+(\\.[0-9]+)*");
std::wstring reference_regex(L"\\[[a-zA-Z0-9, ]+\\]");
std::wstring equation_regex(L"\\([0-9]+(\\.[0-9]+)*\\)");
std::vector<std::pair<int, int>> generic_item_ranges;
std::vector<std::pair<int, int>> reference_ranges;
std::vector<std::pair<int, int>> equation_ranges;
std::vector<std::wstring> generic_item_texts = find_all_regex_matches(content, item_regex, &generic_item_ranges);
std::vector<std::wstring> reference_texts = find_all_regex_matches(content, reference_regex, &reference_ranges);
std::vector<std::wstring> equation_texts = find_all_regex_matches(content, equation_regex, &equation_ranges);
std::vector<PagelessDocumentRect> generic_item_rects;
std::vector<PagelessDocumentRect> reference_rects;
std::vector<PagelessDocumentRect> equation_rects;
int ruler_page = get_vertical_line_page();
get_rects_from_ranges(ruler_page, line_char_rects[line_index], generic_item_ranges, generic_item_rects);
get_rects_from_ranges(ruler_page, line_char_rects[line_index], reference_ranges, reference_rects);
get_rects_from_ranges(ruler_page, line_char_rects[line_index], equation_ranges, equation_rects);
std::vector<SmartViewCandidate> generic_positions;
std::vector<SmartViewCandidate> reference_positions;
std::vector<SmartViewCandidate> equation_positions;
for (int i = 0; i < generic_item_texts.size(); i++) {
std::vector<IndexedData> possible_targets = current_document->find_generic_with_string(generic_item_texts[i], ruler_page);
for (int j = 0; j < possible_targets.size(); j++) {
SmartViewCandidate candid;
candid.doc = get_document();
candid.source_rect = generic_item_rects[i];
candid.source_text = generic_item_texts[i];
candid.target_pos = DocumentPos{ possible_targets[j].page, 0, possible_targets[j].y_offset };
candid.reference_type = ReferenceType::Generic;
generic_positions.push_back(candid);
}
}
for (int i = 0; i < reference_texts.size(); i++) {
if (reference_texts[i].find(L",") != -1) {
QString references_string = QString::fromStdWString(reference_texts[i].substr(1, reference_texts[i].size()-2));
QStringList parts = references_string.split(',');
int n_chars_seen = 1;
for (int j = 0; j < parts.size(); j++) {
auto index = current_document->find_reference_with_string(parts[j].trimmed().toStdWString(), ruler_page);
int rect_range_begin = reference_ranges[i].first + n_chars_seen;
int rect_range_end = rect_range_begin + parts[j].size();
if (index.size() > 0) {
std::vector<PagelessDocumentRect> subrects;
get_rects_from_ranges(ruler_page, line_char_rects[line_index], {std::make_pair(rect_range_begin, rect_range_end)}, subrects);
SmartViewCandidate candid;
candid.doc = get_document();
candid.source_rect = subrects[0];
candid.source_text = parts[j].trimmed().toStdWString();
candid.target_pos = DocumentPos{ index[0].page, 0, index[0].y_offset };
candid.reference_type = ReferenceType::Reference;
reference_positions.push_back(candid);
}
n_chars_seen += parts[j].size() + 1;
}
}
auto index = current_document->find_reference_with_string(reference_texts[i], ruler_page);
if (index.size() > 0) {
SmartViewCandidate candid;
candid.doc = get_document();
candid.source_rect = reference_rects[i];
candid.source_text = reference_texts[i];
candid.target_pos = DocumentPos{ index[0].page, 0, index[0].y_offset };
candid.reference_type = ReferenceType::Reference;
reference_positions.push_back(candid);
}
}
for (int i = 0; i < equation_texts.size(); i++) {
auto index = current_document->find_equation_with_string(equation_texts[i], get_vertical_line_page());
if (index.size() > 0) {
SmartViewCandidate candid;
candid.doc = get_document();
candid.source_rect = equation_rects[i];
candid.source_text = equation_texts[i];
candid.target_pos = DocumentPos{ index[0].page, 0, index[0].y_offset };
candid.reference_type = ReferenceType::Equation;
equation_positions.push_back(candid);
}
}
std::vector<std::vector<SmartViewCandidate>*> res_vectors = { &equation_positions, &reference_positions, &generic_positions };
int index = 0;
int max_size = 0;
for (auto vec : res_vectors) {
if (vec->size() > max_size) {
max_size = vec->size();
}
}
for (int i = 0; i < max_size; i++) {
for (auto vec : res_vectors) {
if (i < vec->size()) {
result.push_back(vec->at(i));
}
}
}
return result;
}
}
return result;
}
bool DocumentView::goto_definition() {
std::vector<SmartViewCandidate> defloc = find_line_definitions();
if (defloc.size() > 0) {
DocumentPos docpos = defloc[0].get_docpos(this);
goto_offset_within_page(docpos.page, docpos.y);
return true;
}
return false;
}
bool DocumentView::get_is_auto_resize_mode() {
return is_auto_resize_mode;
}
void DocumentView::disable_auto_resize_mode() {
this->is_auto_resize_mode = false;
}
void DocumentView::readjust_to_screen() {
this->set_offsets(this->get_offset_x(), this->get_offset_y());
}
float DocumentView::get_half_screen_offset() {
return (static_cast<float>(view_height) / 2.0f);
}
void DocumentView::scroll_mid_to_top() {
float offset = get_half_screen_offset();
move(0, offset);
}
void DocumentView::get_visible_links(std::vector<PdfLink>& visible_page_links) {
std::vector<int> visible_pages;
get_visible_pages(get_view_height(), visible_pages);
for (auto page : visible_pages) {
std::vector<PdfLink> links = get_document()->get_page_merged_pdf_links(page);
for (auto link : links) {
ParsedUri parsed_uri = parse_uri(get_document()->get_mupdf_context(), get_document()->doc, link.uri);
bool is_visible = false;
for (auto& pageless_rect : link.rects) {
NormalizedWindowRect window_rect = DocumentRect(pageless_rect, page).to_window_normalized(this);
if (window_rect.is_visible()) {
is_visible = true;
break;
}
}
if (is_visible) {
visible_page_links.push_back(link);
}
}
}
}
std::deque<AbsoluteRect>* DocumentView::get_selected_character_rects() {
return &this->selected_character_rects;
}
std::optional<AbsoluteRect> DocumentView::get_control_rect() {
if (selected_character_rects.size() > 0) {
if (mark_end) {
return selected_character_rects[selected_character_rects.size() - 1];
}
else {
return selected_character_rects[0];
}
}
return {};
}
std::optional<AbsoluteRect> DocumentView::shrink_selection(bool is_begin, bool word) {
if (selected_character_rects.size() > 1) {
if (word) {
int index = is_begin ? 0 : selected_character_rects.size() - 1;
DocumentRect page_rect = selected_character_rects[index].to_document(current_document);
int page = page_rect.page;
if (page >= 0) {
fz_stext_page* stext_page = current_document->get_stext_with_page_number(page);
std::optional<DocumentRect> new_rect_ = find_shrinking_rect_word(is_begin, stext_page, page_rect);
if (new_rect_) {
AbsoluteRect new_rect = new_rect_->to_absolute(current_document);
if (is_begin) {
while (!are_rects_same(new_rect, selected_character_rects[0])) {
selected_character_rects.pop_front();
if (selected_character_rects.size() == 1) break;
}
return selected_character_rects[0];
}
else {
while (!are_rects_same(new_rect, selected_character_rects[selected_character_rects.size() - 1])) {
selected_character_rects.pop_back();
if (selected_character_rects.size() == 1) break;
}
return selected_character_rects[selected_character_rects.size() - 1];
}
}
return {};
}
}
else {
if (is_begin) {
selected_character_rects.pop_front();
return selected_character_rects[0];
}
else {
selected_character_rects.pop_back();
return selected_character_rects[selected_character_rects.size() - 1];
}
}
}
return {};
}
std::optional<AbsoluteRect> DocumentView::expand_selection(bool is_begin, bool word) {
if (selected_character_rects.size() > 0) {
int index = is_begin ? 0 : selected_character_rects.size() - 1;
DocumentRect page_rect = selected_character_rects[index].to_document(current_document);
if (page_rect.page >= 0) {
fz_stext_page* stext_page = current_document->get_stext_with_page_number(page_rect.page);
std::optional<DocumentRect> next_rect = {};
if (word) {
std::vector<DocumentRect> next_rects_document = find_expanding_rect_word(is_begin, stext_page, page_rect);
std::vector<AbsoluteRect> next_rects;
for (auto dr : next_rects_document) {
next_rects.push_back(dr.to_absolute(current_document));
}
if (is_begin) {
for (int i = 0; i < next_rects.size(); i++) {
selected_character_rects.push_front(next_rects[i]);
}
return selected_character_rects[0];
}
else {
for (int i = 0; i < next_rects.size(); i++) {
selected_character_rects.push_back(next_rects[i]);
}
return selected_character_rects[selected_character_rects.size() - 1];
}
}
else {
next_rect = find_expanding_rect(is_begin, stext_page, page_rect);
}
if (next_rect) {
AbsoluteRect next_rect_abs = next_rect->to_absolute(current_document);
if (is_begin) {
selected_character_rects.push_front(next_rect_abs);
}
else {
selected_character_rects.push_back(next_rect_abs);
}
return next_rect_abs;
}
}
}
return {};
}
void DocumentView::set_text_mark(bool is_begin) {
if (is_begin) {
mark_end = false;
}
else {
mark_end = true;
}
}
void DocumentView::toggle_text_mark() {
set_text_mark(mark_end);
}
WindowPos DocumentView::normalized_window_to_window_pos(NormalizedWindowPos nwp) {
int window_x0 = static_cast<int>(nwp.x * view_width / 2 + view_width / 2);
int window_y0 = static_cast<int>(-nwp.y * view_height / 2 + view_height / 2);
return { window_x0, window_y0 };
}
WindowRect DocumentView::normalized_to_window_rect(NormalizedWindowRect normalized_rect) {
return WindowRect(normalized_rect.top_left().to_window(this), normalized_rect.bottom_right().to_window(this));
}
bool DocumentView::is_ruler_mode() {
return is_ruler_mode_;
}
Document* SmartViewCandidate::get_document(DocumentView* view) {
if (doc) return doc;
return view->get_document();
}
DocumentPos SmartViewCandidate::get_docpos(DocumentView* view) {
if (std::holds_alternative<DocumentPos>(target_pos)) {
return std::get<DocumentPos>(target_pos);
}
else {
return get_document(view)->absolute_to_page_pos_uncentered(std::get<AbsoluteDocumentPos>(target_pos));
}
}
AbsoluteDocumentPos SmartViewCandidate::get_abspos(DocumentView* view) {
if (std::holds_alternative<AbsoluteDocumentPos>(target_pos)) {
return std::get<AbsoluteDocumentPos>(target_pos);
}
else {
return get_document(view)->document_to_absolute_pos(std::get<DocumentPos>(target_pos));
}
}
ScratchPad::ScratchPad() : DocumentView(nullptr, nullptr, nullptr) {
zoom_level = 1;
}
bool ScratchPad::set_offsets(float new_offset_x, float new_offset_y, bool force) {
offset.x = new_offset_x;
offset.y = new_offset_y;
return false;
}
float ScratchPad::set_zoom_level(float zl, bool should_exit_auto_resize_mode, bool readjust) {
float min_zoom_level = 0.1f;
float max_zoom_level = 1000.0f;
if (zl < min_zoom_level) {
zl = min_zoom_level;
}
if (zl > max_zoom_level) {
zl = max_zoom_level;
}
zoom_level = zl;
return zoom_level;
}
float ScratchPad::zoom_in(float zoom_factor, bool readjust) {
return set_zoom_level(zoom_level * zoom_factor, true);
}
float ScratchPad::zoom_out(float zoom_factor, bool readjust) {
return set_zoom_level(zoom_level / zoom_factor, true);
}
std::vector<int> ScratchPad::get_intersecting_drawing_indices(AbsoluteRect selection) {
invalidate_compile();
std::vector<int> res;
for (int i = 0; i < all_drawings.size(); i++) {
for (auto p : all_drawings[i].points) {
if (selection.contains(p.pos)) {
res.push_back(i);
break;
}
}
}
return res;
}
void ScratchPad::delete_intersecting_drawings(AbsoluteRect selection) {
invalidate_compile(true);
std::vector<int> indices = get_intersecting_drawing_indices(selection);
for (int i = 0; i < indices.size(); i++) {
all_drawings.erase(all_drawings.begin() + indices[indices.size() - 1 - i]);
}
}
std::vector<int> ScratchPad::get_intersecting_pixmap_indices(AbsoluteRect selection) {
std::vector<int> res;
for (int i = 0; i < pixmaps.size(); i++) {
if (selection.intersects(pixmaps[i].rect)) {
res.push_back(i);
}
}
return res;
}
void ScratchPad::delete_intersecting_pixmaps(AbsoluteRect selection) {
std::vector<int> indices = get_intersecting_pixmap_indices(selection);
for (int i = 0; i < indices.size(); i++) {
pixmaps.erase(pixmaps.begin() + indices[indices.size() - 1 - i]);
}
}
void ScratchPad::delete_intersecting_objects(AbsoluteRect selection) {
delete_intersecting_drawings(selection);
delete_intersecting_pixmaps(selection);
}
void ScratchPad::get_selected_objects_with_indices(const std::vector<SelectedObjectIndex>&indices, std::vector<FreehandDrawing>&freehand_drawings, std::vector<PixmapDrawing>&pixmap_drawings){
for (auto [index, type] : indices) {
if (type == SelectedObjectType::Drawing) {
freehand_drawings.push_back(all_drawings[index]);
}
else if (type == SelectedObjectType::Pixmap) {
pixmap_drawings.push_back(pixmaps[index]);
}
}
}
void ScratchPad::add_pixmap(QPixmap pixmap) {
AbsoluteDocumentPos top_abs_pos = get_bounding_box().bottom_right();
int top_window_pos = top_abs_pos.to_window(this).y;
float dpr = pixmap.devicePixelRatio();
int pixmap_width = pixmap.width() / dpr;
int pixmap_height = pixmap.height() / dpr;
int pixmap_window_left = view_width / 2 - pixmap_width / 2;
int pixmap_window_right = view_width / 2 + pixmap_width / 2;
int pixmap_window_top = top_window_pos;
int pixmap_window_bottom = top_window_pos + pixmap_height;
WindowPos top_left = { pixmap_window_left, pixmap_window_top };
WindowPos bottom_right = { pixmap_window_right, pixmap_window_bottom };
AbsoluteDocumentPos top_left_abs = top_left.to_absolute(this);
AbsoluteDocumentPos bottom_right_abs = bottom_right.to_absolute(this);
AbsoluteRect pixmap_rect = AbsoluteRect(top_left_abs, bottom_right_abs);
AbsoluteDocumentPos center_pos = pixmap_rect.center();
offset.x = center_pos.x;
offset.y = center_pos.y;
pixmaps.push_back(PixmapDrawing{ pixmap, pixmap_rect });
}
AbsoluteRect ScratchPad::get_bounding_box() {
AbsoluteRect res;
res.x0 = res.x1 = res.y0 = res.y1 = 0;
if (all_drawings.size() > 0) {
res = all_drawings[0].bbox();
}
else if (pixmaps.size() > 0) {
res = pixmaps[0].rect;
}
else {
return res;
}
for (auto drawing : all_drawings) {
res = res.union_rect(drawing.bbox());
}
for (auto [_, pixmap_rect] : pixmaps) {
res = res.union_rect(pixmap_rect);
}
return res;
}
std::vector<SelectedObjectIndex> ScratchPad::get_intersecting_objects(AbsoluteRect selection) {
std::vector<SelectedObjectIndex> selected_objects;
std::vector<int> drawing_indices = get_intersecting_drawing_indices(selection);
std::vector<int> pixmap_indices = get_intersecting_pixmap_indices(selection);
for (auto index : drawing_indices) {
selected_objects.push_back(SelectedObjectIndex{ index, SelectedObjectType::Drawing });
}
for (auto index : pixmap_indices) {
selected_objects.push_back(SelectedObjectIndex{ index, SelectedObjectType::Pixmap });
}
return selected_objects;
}
const std::vector<FreehandDrawing>& ScratchPad::get_all_drawings() {
return all_drawings;
}
const std::vector<FreehandDrawing>& ScratchPad::get_non_compiled_drawings() {
return non_compiled_drawings;
}
void ScratchPad::on_compile() {
is_compile_valid = true;
non_compiled_drawings.clear();
}
void ScratchPad::invalidate_compile(bool force) {
if (non_compiled_drawings.size() > 0) {
is_compile_valid = false;
}
if (force) {
is_compile_valid = false;
}
}
void ScratchPad::add_drawing(FreehandDrawing drawing) {
all_drawings.push_back(drawing);
non_compiled_drawings.push_back(drawing);
}
void ScratchPad::clear() {
all_drawings.clear();
pixmaps.clear();
non_compiled_drawings.clear();
is_compile_valid = false;
}
bool ScratchPad::is_compile_invalid() {
return !is_compile_valid;
}
std::vector<int> DocumentView::get_visible_bookmark_indices() {
const std::vector<BookMark>& bookmarks = get_document()->get_bookmarks();
std::vector<int> res;
for (int i = 0; i < bookmarks.size(); i++) {
if (bookmarks[i].is_marked() || bookmarks[i].is_freetext()) {
if (bookmarks[i].get_rectangle().to_window_normalized(this).is_visible()) {
res.push_back(i);
}
}
}
return res;
}
std::vector<int> DocumentView::get_visible_highlight_indices() {
const std::vector<Highlight>& highlights = get_document()->get_highlights();
std::vector<int> res;
for (size_t i = 0; i < highlights.size(); i++) {
NormalizedWindowPos selection_begin_window_pos = absolute_to_window_pos(
{ highlights[i].selection_begin.x, highlights[i].selection_begin.y }
);
NormalizedWindowPos selection_end_window_pos = absolute_to_window_pos(
{ highlights[i].selection_end.x, highlights[i].selection_end.y }
);
if (selection_begin_window_pos.y > selection_end_window_pos.y) {
std::swap(selection_begin_window_pos.y, selection_end_window_pos.y);
}
if (range_intersects(selection_begin_window_pos.y, selection_end_window_pos.y, -1.0f, 1.0f)) {
res.push_back(i);
}
}
return res;
}
void DocumentView::set_presentation_page_number(std::optional<int> page) {
presentation_page_number = page;
}
std::optional<int> DocumentView::get_presentation_page_number() {
return presentation_page_number;
}
bool DocumentView::is_presentation_mode() {
return presentation_page_number.has_value();
}
VirtualPos DocumentView::absolute_to_virtual_pos(const AbsoluteDocumentPos& abspos) {
if (fast_coordinates()) {
return VirtualPos{ abspos.x, abspos.y };
}
fill_cached_virtual_rects();
if (cached_virtual_rects.size() == 0) {
return VirtualPos{ abspos.x, abspos.y };
}
DocumentPos docpos = abspos.to_document(current_document);
return document_to_virtual_pos(docpos);
}
VirtualPos DocumentView::document_to_virtual_pos(DocumentPos docpos) {
if (fast_coordinates()) {
AbsoluteDocumentPos abspos = docpos.to_absolute(current_document);
return { abspos.x, abspos.y };
}
else {
VirtualRect page_virtual_rect = cached_virtual_rects[docpos.page];
VirtualPos pos = page_virtual_rect.top_left();
if (SAME_WIDTH && same_width_mode_first_page_width.has_value()) {
float factor = static_cast<float>(same_width_mode_first_page_width.value()) / current_document->get_page_width(docpos.page);
docpos.x *= factor;
docpos.y *= factor;
}
pos.x += docpos.x;
pos.y += docpos.y;
return pos;
}
}
AbsoluteDocumentPos DocumentView::virtual_to_absolute_pos(const VirtualPos& vpos) {
if (fast_coordinates()) {
return AbsoluteDocumentPos{ vpos.x, vpos.y };
}
fill_cached_virtual_rects();
if (cached_virtual_rects.size() == 0) {
return AbsoluteDocumentPos{ vpos.x, vpos.y };
}
int page = -1;
for (int i = 0; i < cached_virtual_rects.size(); i++) {
if (cached_virtual_rects[i].y1 > vpos.y) {
page = i;
break;
}
}
for (int i = 0; i < cached_virtual_rects.size(); i++) {
if (cached_virtual_rects[i].contains(vpos)) {
page = i;
break;
}
}
if (page == -1) {
page = cached_virtual_rects.size() - 1;
}
DocumentPos docpos;
docpos.x = vpos.x - cached_virtual_rects[page].x0;
docpos.y = vpos.y - cached_virtual_rects[page].y0;
docpos.page = page;
if (SAME_WIDTH && same_width_mode_first_page_width.has_value()) {
float factor = static_cast<float>(same_width_mode_first_page_width.value()) / current_document->get_page_width(page);
docpos.x /= factor;
docpos.y /= factor;
}
return docpos.to_absolute(current_document);
}
void DocumentView::fill_cached_virtual_rects(bool force) {
if (!current_document) return;
bool page_dims_are_loaded = current_document->can_use_highlights();
if (!page_dims_are_loaded) {
needs_refill = true;
}
else {
if (needs_refill){
force = true;
}
needs_refill = false;
}
float cum_offset = 0;
if ((cached_virtual_rects.size() == 0) || force) {
cached_virtual_rects.clear();
int num_pages = current_document->num_pages();
if (num_pages == 0) return;
if (two_page_mode) {
for (int i = 0; i < num_pages; i++) {
float page_width = current_document->get_page_width(i);
float page_height = current_document->get_page_height(i);
VirtualRect page_rect;
page_rect.x0 = -page_width / 2;
page_rect.x1 = page_width / 2;
page_rect.y0 = cum_offset;
page_rect.y1 = cum_offset + page_height;
float mult = 1.0f;
if ((i + (int)RECTO_VERSO_ADJUSTMENT) % 2 == 1) {
cum_offset += page_height + page_space_y;
}
else {
mult = -1.0f;
}
if (page_space_x >= 0) {
page_rect.x0 += mult * (page_width + page_space_x) / 2;
page_rect.x1 += mult * (page_width + page_space_x) / 2;
}
else {
page_rect.x0 += mult * (page_width / 2) + mult * page_space_x;
page_rect.x1 += mult * (page_width / 2) + mult * page_space_x;
}
cached_virtual_rects.push_back(page_rect);
}
}
else {
cached_virtual_rects.clear();
if (REAL_PAGE_SEPARATION || SAME_WIDTH) {
int first_page_width = current_document->get_page_width_median();
if (SAME_WIDTH) {
same_width_mode_first_page_width = first_page_width;
}
for (int i = 0; i < num_pages; i++) {
float page_width = current_document->get_page_width(i);
float page_height = current_document->get_page_height(i);
if (SAME_WIDTH) {
float factor = static_cast<float>(first_page_width) / page_width;
page_width *= factor;
page_height *= factor;
}
VirtualRect page_rect;
page_rect.x0 = -page_width / 2;
page_rect.x1 = page_width / 2;
page_rect.y0 = cum_offset;
page_rect.y1 = cum_offset + page_height;
cached_virtual_rects.push_back(page_rect);
cum_offset += page_height + page_space_y;
}
}
}
}
if (!needs_refill) {
min_virtual_x = FLT_MAX;
max_virtual_x = FLT_MIN;
max_virtual_y = FLT_MIN;
for (auto r : cached_virtual_rects) {
min_virtual_x = std::min(min_virtual_x, r.x0);
max_virtual_x = std::max(max_virtual_x, r.x1);
max_virtual_y = std::max(max_virtual_y, r.y1);
}
}
}
VirtualPos DocumentView::window_to_virtual_pos(const WindowPos& window_pos) {
float vx = (window_pos.x - view_width / 2) / zoom_level - offset.x;
float vy = (window_pos.y - view_height / 2) / zoom_level + offset.y;
return { vx, vy };
}
WindowPos DocumentView::virtual_to_window_pos(const VirtualPos& vpos) {
WindowPos res;
res.x = (vpos.x + offset.x)* zoom_level + view_width / 2;
res.y = (vpos.y - offset.y)* zoom_level + view_height / 2;
return res;
}
void DocumentView::toggle_two_page(){
AbsoluteDocumentPos current_abs_offset = get_offsets();
two_page_mode = !two_page_mode;
cached_virtual_rects.clear();
fill_cached_virtual_rects();
set_offsets(current_abs_offset.x, current_abs_offset.y);
fit_to_page_width();
}
NormalizedWindowRect DocumentView::virtual_to_normalized_window_rect(const VirtualRect& virtual_rect){
NormalizedWindowPos top_left = virtual_to_window_pos(VirtualPos{ virtual_rect.x0, virtual_rect.y0 }).to_window_normalized(this);
NormalizedWindowPos bottom_right = virtual_to_window_pos(VirtualPos{ virtual_rect.x1, virtual_rect.y1 }).to_window_normalized(this);
return NormalizedWindowRect(top_left, bottom_right);
}
bool DocumentView::is_two_page_mode() {
return two_page_mode;
}
void DocumentView::set_page_space_x(float space_x) {
page_space_x = space_x;
}
void DocumentView::set_page_space_y(float space_y) {
page_space_y = space_y;
}
float DocumentView::get_page_space_x() {
return page_space_x;
}
float DocumentView::get_page_space_y() {
return page_space_x;
}
bool DocumentView::fast_coordinates() {
return (!two_page_mode) && (!REAL_PAGE_SEPARATION) && (!SAME_WIDTH);
}